mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-04 17:45:07 +08:00
Compare commits
7 Commits
v0.20.5
...
df8d31451b
| Author | SHA1 | Date | |
|---|---|---|---|
| df8d31451b | |||
| fc95d113c3 | |||
| 7d14455fbe | |||
| bbe6ed3b90 | |||
| 127af4e45c | |||
| 41cdba19ba | |||
| 0d9c1f1c3c |
@ -22,10 +22,10 @@ from openpyxl import Workbook, load_workbook
|
|||||||
from rag.nlp import find_codec
|
from rag.nlp import find_codec
|
||||||
|
|
||||||
# copied from `/openpyxl/cell/cell.py`
|
# copied from `/openpyxl/cell/cell.py`
|
||||||
ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
|
ILLEGAL_CHARACTERS_RE = re.compile(r"[\000-\010]|[\013-\014]|[\016-\037]")
|
||||||
|
|
||||||
|
|
||||||
class RAGFlowExcelParser:
|
class RAGFlowExcelParser:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_excel_to_workbook(file_like_object):
|
def _load_excel_to_workbook(file_like_object):
|
||||||
if isinstance(file_like_object, bytes):
|
if isinstance(file_like_object, bytes):
|
||||||
@ -36,7 +36,7 @@ class RAGFlowExcelParser:
|
|||||||
file_head = file_like_object.read(4)
|
file_head = file_like_object.read(4)
|
||||||
file_like_object.seek(0)
|
file_like_object.seek(0)
|
||||||
|
|
||||||
if not (file_head.startswith(b'PK\x03\x04') or file_head.startswith(b'\xD0\xCF\x11\xE0')):
|
if not (file_head.startswith(b"PK\x03\x04") or file_head.startswith(b"\xd0\xcf\x11\xe0")):
|
||||||
logging.info("Not an Excel file, converting CSV to Excel Workbook")
|
logging.info("Not an Excel file, converting CSV to Excel Workbook")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -48,7 +48,7 @@ class RAGFlowExcelParser:
|
|||||||
raise Exception(f"Failed to parse CSV and convert to Excel Workbook: {e_csv}")
|
raise Exception(f"Failed to parse CSV and convert to Excel Workbook: {e_csv}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return load_workbook(file_like_object,data_only= True)
|
return load_workbook(file_like_object, data_only=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(f"openpyxl load error: {e}, try pandas instead")
|
logging.info(f"openpyxl load error: {e}, try pandas instead")
|
||||||
try:
|
try:
|
||||||
@ -59,7 +59,7 @@ class RAGFlowExcelParser:
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logging.info(f"pandas with default engine load error: {ex}, try calamine instead")
|
logging.info(f"pandas with default engine load error: {ex}, try calamine instead")
|
||||||
file_like_object.seek(0)
|
file_like_object.seek(0)
|
||||||
df = pd.read_excel(file_like_object, engine='calamine')
|
df = pd.read_excel(file_like_object, engine="calamine")
|
||||||
return RAGFlowExcelParser._dataframe_to_workbook(df)
|
return RAGFlowExcelParser._dataframe_to_workbook(df)
|
||||||
except Exception as e_pandas:
|
except Exception as e_pandas:
|
||||||
raise Exception(f"pandas.read_excel error: {e_pandas}, original openpyxl error: {e}")
|
raise Exception(f"pandas.read_excel error: {e_pandas}, original openpyxl error: {e}")
|
||||||
@ -116,9 +116,7 @@ class RAGFlowExcelParser:
|
|||||||
tb = ""
|
tb = ""
|
||||||
tb += f"<table><caption>{sheetname}</caption>"
|
tb += f"<table><caption>{sheetname}</caption>"
|
||||||
tb += tb_rows_0
|
tb += tb_rows_0
|
||||||
for r in list(
|
for r in list(rows[1 + chunk_i * chunk_rows : min(1 + (chunk_i + 1) * chunk_rows, len(rows))]):
|
||||||
rows[1 + chunk_i * chunk_rows: min(1 + (chunk_i + 1) * chunk_rows, len(rows))]
|
|
||||||
):
|
|
||||||
tb += "<tr>"
|
tb += "<tr>"
|
||||||
for i, c in enumerate(r):
|
for i, c in enumerate(r):
|
||||||
if c.value is None:
|
if c.value is None:
|
||||||
@ -133,8 +131,16 @@ class RAGFlowExcelParser:
|
|||||||
|
|
||||||
def markdown(self, fnm):
|
def markdown(self, fnm):
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm
|
file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm
|
||||||
df = pd.read_excel(file_like_object)
|
try:
|
||||||
|
file_like_object.seek(0)
|
||||||
|
df = pd.read_excel(file_like_object)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"Parse spreadsheet error: {e}, trying to interpret as CSV file")
|
||||||
|
file_like_object.seek(0)
|
||||||
|
df = pd.read_csv(file_like_object)
|
||||||
|
df = df.replace(r"^\s*$", "", regex=True)
|
||||||
return df.to_markdown(index=False)
|
return df.to_markdown(index=False)
|
||||||
|
|
||||||
def __call__(self, fnm):
|
def __call__(self, fnm):
|
||||||
|
|||||||
@ -29,7 +29,6 @@ redis:
|
|||||||
db: 1
|
db: 1
|
||||||
password: '${REDIS_PASSWORD:-infini_rag_flow}'
|
password: '${REDIS_PASSWORD:-infini_rag_flow}'
|
||||||
host: '${REDIS_HOST:-redis}:6379'
|
host: '${REDIS_HOST:-redis}:6379'
|
||||||
|
|
||||||
# postgres:
|
# postgres:
|
||||||
# name: '${POSTGRES_DBNAME:-rag_flow}'
|
# name: '${POSTGRES_DBNAME:-rag_flow}'
|
||||||
# user: '${POSTGRES_USER:-rag_flow}'
|
# user: '${POSTGRES_USER:-rag_flow}'
|
||||||
@ -65,15 +64,26 @@ redis:
|
|||||||
# secret: 'secret'
|
# secret: 'secret'
|
||||||
# tenant_id: 'tenant_id'
|
# tenant_id: 'tenant_id'
|
||||||
# container_name: 'container_name'
|
# container_name: 'container_name'
|
||||||
|
# The OSS object storage uses the MySQL configuration above by default. If you need to switch to another object storage service, please uncomment and configure the following parameters.
|
||||||
|
# opendal:
|
||||||
|
# scheme: 'mysql' # Storage type, such as s3, oss, azure, etc.
|
||||||
|
# config:
|
||||||
|
# oss_table: 'opendal_storage'
|
||||||
# user_default_llm:
|
# user_default_llm:
|
||||||
# factory: 'Tongyi-Qianwen'
|
# factory: 'BAAI'
|
||||||
# api_key: 'sk-xxxxxxxxxxxxx'
|
# api_key: 'backup'
|
||||||
# base_url: ''
|
# base_url: 'backup_base_url'
|
||||||
# default_models:
|
# default_models:
|
||||||
# chat_model: 'qwen-plus'
|
# chat_model:
|
||||||
# embedding_model: 'BAAI/bge-large-zh-v1.5@BAAI'
|
# name: 'qwen2.5-7b-instruct'
|
||||||
# rerank_model: ''
|
# factory: 'xxxx'
|
||||||
# asr_model: ''
|
# api_key: 'xxxx'
|
||||||
|
# base_url: 'https://api.xx.com'
|
||||||
|
# embedding_model:
|
||||||
|
# name: 'bge-m3'
|
||||||
|
# rerank_model: 'bge-reranker-v2'
|
||||||
|
# asr_model:
|
||||||
|
# model: 'whisper-large-v3' # alias of name
|
||||||
# image2text_model: ''
|
# image2text_model: ''
|
||||||
# oauth:
|
# oauth:
|
||||||
# oauth2:
|
# oauth2:
|
||||||
@ -109,3 +119,14 @@ redis:
|
|||||||
# switch: false
|
# switch: false
|
||||||
# component: false
|
# component: false
|
||||||
# dataset: false
|
# dataset: false
|
||||||
|
# smtp:
|
||||||
|
# mail_server: ""
|
||||||
|
# mail_port: 465
|
||||||
|
# mail_use_ssl: true
|
||||||
|
# mail_use_tls: false
|
||||||
|
# mail_username: ""
|
||||||
|
# mail_password: ""
|
||||||
|
# mail_default_sender:
|
||||||
|
# - "RAGFlow" # display name
|
||||||
|
# - "" # sender email address
|
||||||
|
# mail_frontend_url: "https://your-frontend.example.com"
|
||||||
|
|||||||
@ -73,11 +73,13 @@ class Chunker(ProcessBase):
|
|||||||
|
|
||||||
def _general(self, from_upstream: ChunkerFromUpstream):
|
def _general(self, from_upstream: ChunkerFromUpstream):
|
||||||
self.callback(random.randint(1, 5) / 100.0, "Start to chunk via `General`.")
|
self.callback(random.randint(1, 5) / 100.0, "Start to chunk via `General`.")
|
||||||
if from_upstream.output_format in ["markdown", "text"]:
|
if from_upstream.output_format in ["markdown", "text", "html"]:
|
||||||
if from_upstream.output_format == "markdown":
|
if from_upstream.output_format == "markdown":
|
||||||
payload = from_upstream.markdown_result
|
payload = from_upstream.markdown_result
|
||||||
else: # == "text"
|
elif from_upstream.output_format == "text":
|
||||||
payload = from_upstream.text_result
|
payload = from_upstream.text_result
|
||||||
|
else: # == "html"
|
||||||
|
payload = from_upstream.html_result
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
payload = ""
|
payload = ""
|
||||||
@ -90,6 +92,7 @@ class Chunker(ProcessBase):
|
|||||||
)
|
)
|
||||||
return [{"text": c} for c in cks]
|
return [{"text": c} for c in cks]
|
||||||
|
|
||||||
|
# json
|
||||||
sections, section_images = [], []
|
sections, section_images = [], []
|
||||||
for o in from_upstream.json_result or []:
|
for o in from_upstream.json_result or []:
|
||||||
sections.append((o.get("text", ""), o.get("position_tag", "")))
|
sections.append((o.get("text", ""), o.get("position_tag", "")))
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class ChunkerFromUpstream(BaseModel):
|
|||||||
json_result: list[dict[str, Any]] | None = Field(default=None, alias="json")
|
json_result: list[dict[str, Any]] | None = Field(default=None, alias="json")
|
||||||
markdown_result: str | None = Field(default=None, alias="markdown")
|
markdown_result: str | None = Field(default=None, alias="markdown")
|
||||||
text_result: str | None = Field(default=None, alias="text")
|
text_result: str | None = Field(default=None, alias="text")
|
||||||
html_result: str | None = Field(default=None, alias="html")
|
html_result: list[str] | None = Field(default=None, alias="html")
|
||||||
|
|
||||||
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
@ -29,8 +30,18 @@ class ParserParam(ProcessParamBase):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.allowed_output_format = {
|
self.allowed_output_format = {
|
||||||
"pdf": ["json", "markdown"],
|
"pdf": [
|
||||||
"excel": ["json", "markdown", "html"],
|
"json",
|
||||||
|
"markdown",
|
||||||
|
],
|
||||||
|
"spreadsheet": [
|
||||||
|
"json",
|
||||||
|
"markdown",
|
||||||
|
"html",
|
||||||
|
],
|
||||||
|
"word": [
|
||||||
|
"json",
|
||||||
|
],
|
||||||
"ppt": [],
|
"ppt": [],
|
||||||
"image": [],
|
"image": [],
|
||||||
"email": [],
|
"email": [],
|
||||||
@ -44,12 +55,29 @@ class ParserParam(ProcessParamBase):
|
|||||||
"parse_method": "deepdoc", # deepdoc/plain_text/vlm
|
"parse_method": "deepdoc", # deepdoc/plain_text/vlm
|
||||||
"vlm_name": "",
|
"vlm_name": "",
|
||||||
"lang": "Chinese",
|
"lang": "Chinese",
|
||||||
"suffix": ["pdf"],
|
"suffix": [
|
||||||
|
"pdf",
|
||||||
|
],
|
||||||
"output_format": "json",
|
"output_format": "json",
|
||||||
},
|
},
|
||||||
"excel": {
|
"spreadsheet": {
|
||||||
"output_format": "html",
|
"output_format": "html",
|
||||||
"suffix": ["xls", "xlsx", "csv"],
|
"suffix": [
|
||||||
|
"xls",
|
||||||
|
"xlsx",
|
||||||
|
"csv",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"word": {
|
||||||
|
"suffix": [
|
||||||
|
"doc",
|
||||||
|
"docx",
|
||||||
|
],
|
||||||
|
"output_format": "json",
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"suffix": ["md", "markdown"],
|
||||||
|
"output_format": "json",
|
||||||
},
|
},
|
||||||
"ppt": {},
|
"ppt": {},
|
||||||
"image": {
|
"image": {
|
||||||
@ -76,10 +104,15 @@ class ParserParam(ProcessParamBase):
|
|||||||
pdf_output_format = pdf_config.get("output_format", "")
|
pdf_output_format = pdf_config.get("output_format", "")
|
||||||
self.check_valid_value(pdf_output_format, "PDF output format abnormal.", self.allowed_output_format["pdf"])
|
self.check_valid_value(pdf_output_format, "PDF output format abnormal.", self.allowed_output_format["pdf"])
|
||||||
|
|
||||||
excel_config = self.setups.get("excel", "")
|
spreadsheet_config = self.setups.get("spreadsheet", "")
|
||||||
if excel_config:
|
if spreadsheet_config:
|
||||||
excel_output_format = excel_config.get("output_format", "")
|
spreadsheet_output_format = spreadsheet_config.get("output_format", "")
|
||||||
self.check_valid_value(excel_output_format, "Excel output format abnormal.", self.allowed_output_format["excel"])
|
self.check_valid_value(spreadsheet_output_format, "Spreadsheet output format abnormal.", self.allowed_output_format["spreadsheet"])
|
||||||
|
|
||||||
|
doc_config = self.setups.get("doc", "")
|
||||||
|
if doc_config:
|
||||||
|
doc_output_format = doc_config.get("output_format", "")
|
||||||
|
self.check_valid_value(doc_output_format, "Word processer document output format abnormal.", self.allowed_output_format["doc"])
|
||||||
|
|
||||||
image_config = self.setups.get("image", "")
|
image_config = self.setups.get("image", "")
|
||||||
if image_config:
|
if image_config:
|
||||||
@ -93,10 +126,13 @@ class ParserParam(ProcessParamBase):
|
|||||||
class Parser(ProcessBase):
|
class Parser(ProcessBase):
|
||||||
component_name = "Parser"
|
component_name = "Parser"
|
||||||
|
|
||||||
def _pdf(self, blob):
|
def _pdf(self, from_upstream: ParserFromUpstream):
|
||||||
self.callback(random.randint(1, 5) / 100.0, "Start to work on a PDF.")
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on a PDF.")
|
||||||
|
|
||||||
|
blob = from_upstream.blob
|
||||||
conf = self._param.setups["pdf"]
|
conf = self._param.setups["pdf"]
|
||||||
self.set_output("output_format", conf["output_format"])
|
self.set_output("output_format", conf["output_format"])
|
||||||
|
|
||||||
if conf.get("parse_method") == "deepdoc":
|
if conf.get("parse_method") == "deepdoc":
|
||||||
bboxes = RAGFlowPdfParser().parse_into_bboxes(blob, callback=self.callback)
|
bboxes = RAGFlowPdfParser().parse_into_bboxes(blob, callback=self.callback)
|
||||||
elif conf.get("parse_method") == "plain_text":
|
elif conf.get("parse_method") == "plain_text":
|
||||||
@ -110,6 +146,7 @@ class Parser(ProcessBase):
|
|||||||
for t, poss in lines:
|
for t, poss in lines:
|
||||||
pn, x0, x1, top, bott = poss.split(" ")
|
pn, x0, x1, top, bott = poss.split(" ")
|
||||||
bboxes.append({"page_number": int(pn), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t})
|
bboxes.append({"page_number": int(pn), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t})
|
||||||
|
|
||||||
if conf.get("output_format") == "json":
|
if conf.get("output_format") == "json":
|
||||||
self.set_output("json", bboxes)
|
self.set_output("json", bboxes)
|
||||||
if conf.get("output_format") == "markdown":
|
if conf.get("output_format") == "markdown":
|
||||||
@ -123,23 +160,93 @@ class Parser(ProcessBase):
|
|||||||
mkdn += b.get("text", "") + "\n"
|
mkdn += b.get("text", "") + "\n"
|
||||||
self.set_output("markdown", mkdn)
|
self.set_output("markdown", mkdn)
|
||||||
|
|
||||||
def _excel(self, blob):
|
def _spreadsheet(self, from_upstream: ParserFromUpstream):
|
||||||
self.callback(random.randint(1, 5) / 100.0, "Start to work on a Excel.")
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on a Spreadsheet.")
|
||||||
conf = self._param.setups["excel"]
|
|
||||||
|
blob = from_upstream.blob
|
||||||
|
conf = self._param.setups["spreadsheet"]
|
||||||
self.set_output("output_format", conf["output_format"])
|
self.set_output("output_format", conf["output_format"])
|
||||||
excel_parser = ExcelParser()
|
|
||||||
|
print("spreadsheet {conf=}", flush=True)
|
||||||
|
spreadsheet_parser = ExcelParser()
|
||||||
if conf.get("output_format") == "html":
|
if conf.get("output_format") == "html":
|
||||||
html = excel_parser.html(blob, 1000000000)
|
html = spreadsheet_parser.html(blob, 1000000000)
|
||||||
self.set_output("html", html)
|
self.set_output("html", html)
|
||||||
elif conf.get("output_format") == "json":
|
elif conf.get("output_format") == "json":
|
||||||
self.set_output("json", [{"text": txt} for txt in excel_parser(blob) if txt])
|
self.set_output("json", [{"text": txt} for txt in spreadsheet_parser(blob) if txt])
|
||||||
elif conf.get("output_format") == "markdown":
|
elif conf.get("output_format") == "markdown":
|
||||||
self.set_output("markdown", excel_parser.markdown(blob))
|
self.set_output("markdown", spreadsheet_parser.markdown(blob))
|
||||||
|
|
||||||
|
def _word(self, from_upstream: ParserFromUpstream):
|
||||||
|
from tika import parser as word_parser
|
||||||
|
|
||||||
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on a Word Processor Document")
|
||||||
|
|
||||||
|
blob = from_upstream.blob
|
||||||
|
name = from_upstream.name
|
||||||
|
conf = self._param.setups["word"]
|
||||||
|
self.set_output("output_format", conf["output_format"])
|
||||||
|
|
||||||
|
print("word {conf=}", flush=True)
|
||||||
|
doc_parsed = word_parser.from_buffer(blob)
|
||||||
|
|
||||||
|
sections = []
|
||||||
|
if doc_parsed.get("content"):
|
||||||
|
sections = doc_parsed["content"].split("\n")
|
||||||
|
sections = [{"text": section} for section in sections if section]
|
||||||
|
else:
|
||||||
|
logging.warning(f"tika.parser got empty content from {name}.")
|
||||||
|
|
||||||
|
# json
|
||||||
|
assert conf.get("output_format") == "json", "have to be json for doc"
|
||||||
|
if conf.get("output_format") == "json":
|
||||||
|
self.set_output("json", sections)
|
||||||
|
|
||||||
|
def _markdown(self, from_upstream: ParserFromUpstream):
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
from rag.app.naive import Markdown as naive_markdown_parser
|
||||||
|
from rag.nlp import concat_img
|
||||||
|
|
||||||
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on a Word Processor Document")
|
||||||
|
|
||||||
|
blob = from_upstream.blob
|
||||||
|
name = from_upstream.name
|
||||||
|
conf = self._param.setups["markdown"]
|
||||||
|
self.set_output("output_format", conf["output_format"])
|
||||||
|
|
||||||
|
print("markdown {conf=}", flush=True)
|
||||||
|
|
||||||
|
markdown_parser = naive_markdown_parser()
|
||||||
|
sections, tables = markdown_parser(name, blob, separate_tables=False)
|
||||||
|
|
||||||
|
# json
|
||||||
|
assert conf.get("output_format") == "json", "have to be json for doc"
|
||||||
|
if conf.get("output_format") == "json":
|
||||||
|
json_results = []
|
||||||
|
|
||||||
|
for section_text, _ in sections:
|
||||||
|
json_result = {
|
||||||
|
"text": section_text,
|
||||||
|
}
|
||||||
|
|
||||||
|
images = markdown_parser.get_pictures(section_text) if section_text else None
|
||||||
|
if images:
|
||||||
|
# If multiple images found, combine them using concat_img
|
||||||
|
combined_image = reduce(concat_img, images) if len(images) > 1 else images[0]
|
||||||
|
json_result["image"] = combined_image
|
||||||
|
|
||||||
|
json_results.append(json_result)
|
||||||
|
|
||||||
|
self.set_output("json", json_results)
|
||||||
|
|
||||||
|
|
||||||
async def _invoke(self, **kwargs):
|
async def _invoke(self, **kwargs):
|
||||||
function_map = {
|
function_map = {
|
||||||
"pdf": self._pdf,
|
"pdf": self._pdf,
|
||||||
"excel": self._excel,
|
"markdown": self._markdown,
|
||||||
|
"spreadsheet": self._spreadsheet,
|
||||||
|
"word": self._word
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
from_upstream = ParserFromUpstream.model_validate(kwargs)
|
from_upstream = ParserFromUpstream.model_validate(kwargs)
|
||||||
@ -150,5 +257,5 @@ class Parser(ProcessBase):
|
|||||||
for p_type, conf in self._param.setups.items():
|
for p_type, conf in self._param.setups.items():
|
||||||
if from_upstream.name.split(".")[-1].lower() not in conf.get("suffix", []):
|
if from_upstream.name.split(".")[-1].lower() not in conf.get("suffix", []):
|
||||||
continue
|
continue
|
||||||
await trio.to_thread.run_sync(function_map[p_type], from_upstream.blob)
|
await trio.to_thread.run_sync(function_map[p_type], from_upstream)
|
||||||
break
|
break
|
||||||
|
|||||||
@ -23,16 +23,31 @@
|
|||||||
],
|
],
|
||||||
"output_format": "json"
|
"output_format": "json"
|
||||||
},
|
},
|
||||||
"excel": {
|
"spreadsheet": {
|
||||||
"output_format": "html",
|
|
||||||
"suffix": [
|
"suffix": [
|
||||||
"xls",
|
"xls",
|
||||||
"xlsx",
|
"xlsx",
|
||||||
"csv"
|
"csv"
|
||||||
]
|
],
|
||||||
|
"output_format": "html"
|
||||||
|
},
|
||||||
|
"word": {
|
||||||
|
"suffix": [
|
||||||
|
"doc",
|
||||||
|
"docx"
|
||||||
|
],
|
||||||
|
"output_format": "json"
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"suffix": [
|
||||||
|
"md",
|
||||||
|
"markdown"
|
||||||
|
],
|
||||||
|
"output_format": "json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"downstream": ["Chunker:0"],
|
"downstream": ["Chunker:0"],
|
||||||
"upstream": ["Begin"]
|
"upstream": ["Begin"]
|
||||||
|
|||||||
@ -31,7 +31,7 @@ class TokenizerFromUpstream(BaseModel):
|
|||||||
json_result: list[dict[str, Any]] | None = Field(default=None, alias="json")
|
json_result: list[dict[str, Any]] | None = Field(default=None, alias="json")
|
||||||
markdown_result: str | None = Field(default=None, alias="markdown")
|
markdown_result: str | None = Field(default=None, alias="markdown")
|
||||||
text_result: str | None = Field(default=None, alias="text")
|
text_result: str | None = Field(default=None, alias="text")
|
||||||
html_result: str | None = Field(default=None, alias="html")
|
html_result: list[str] | None = Field(default=None, alias="html")
|
||||||
|
|
||||||
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
||||||
|
|
||||||
|
|||||||
@ -117,11 +117,13 @@ class Tokenizer(ProcessBase):
|
|||||||
ck["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(ck["content_ltks"])
|
ck["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(ck["content_ltks"])
|
||||||
if i % 100 == 99:
|
if i % 100 == 99:
|
||||||
self.callback(i * 1.0 / len(chunks) / parts)
|
self.callback(i * 1.0 / len(chunks) / parts)
|
||||||
elif from_upstream.output_format in ["markdown", "text"]:
|
elif from_upstream.output_format in ["markdown", "text", "html"]:
|
||||||
if from_upstream.output_format == "markdown":
|
if from_upstream.output_format == "markdown":
|
||||||
payload = from_upstream.markdown_result
|
payload = from_upstream.markdown_result
|
||||||
else: # == "text"
|
elif from_upstream.output_format == "text":
|
||||||
payload = from_upstream.text_result
|
payload = from_upstream.text_result
|
||||||
|
else: # == "html"
|
||||||
|
payload = from_upstream.html_result
|
||||||
|
|
||||||
if not payload:
|
if not payload:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@ -124,17 +124,19 @@ class Base(ABC):
|
|||||||
mime = "image/jpeg"
|
mime = "image/jpeg"
|
||||||
b64 = base64.b64encode(data).decode("utf-8")
|
b64 = base64.b64encode(data).decode("utf-8")
|
||||||
return f"data:{mime};base64,{b64}"
|
return f"data:{mime};base64,{b64}"
|
||||||
buffered = BytesIO()
|
with BytesIO() as buffered:
|
||||||
fmt = "JPEG"
|
fmt = "JPEG"
|
||||||
try:
|
try:
|
||||||
image.save(buffered, format="JPEG")
|
image.save(buffered, format="JPEG")
|
||||||
except Exception:
|
except Exception:
|
||||||
buffered = BytesIO() # reset buffer before saving PNG
|
# reset buffer before saving PNG
|
||||||
image.save(buffered, format="PNG")
|
buffered.seek(0)
|
||||||
fmt = "PNG"
|
buffered.truncate()
|
||||||
data = buffered.getvalue()
|
image.save(buffered, format="PNG")
|
||||||
b64 = base64.b64encode(data).decode("utf-8")
|
fmt = "PNG"
|
||||||
mime = f"image/{fmt.lower()}"
|
data = buffered.getvalue()
|
||||||
|
b64 = base64.b64encode(data).decode("utf-8")
|
||||||
|
mime = f"image/{fmt.lower()}"
|
||||||
return f"data:{mime};base64,{b64}"
|
return f"data:{mime};base64,{b64}"
|
||||||
|
|
||||||
def prompt(self, b64):
|
def prompt(self, b64):
|
||||||
|
|||||||
@ -751,6 +751,8 @@ class SILICONFLOWEmbed(Base):
|
|||||||
token_count = 0
|
token_count = 0
|
||||||
for i in range(0, len(texts), batch_size):
|
for i in range(0, len(texts), batch_size):
|
||||||
texts_batch = texts[i : i + batch_size]
|
texts_batch = texts[i : i + batch_size]
|
||||||
|
texts_batch = [" " if not text.strip() else text for text in texts_batch]
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.model_name,
|
"model": self.model_name,
|
||||||
"input": texts_batch,
|
"input": texts_batch,
|
||||||
|
|||||||
@ -518,7 +518,7 @@ def hierarchical_merge(bull, sections, depth):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def naive_merge(sections, chunk_token_num=128, delimiter="\n。;!?", overlapped_percent=0):
|
def naive_merge(sections: str | list, chunk_token_num=128, delimiter="\n。;!?", overlapped_percent=0):
|
||||||
from deepdoc.parser.pdf_parser import RAGFlowPdfParser
|
from deepdoc.parser.pdf_parser import RAGFlowPdfParser
|
||||||
if not sections:
|
if not sections:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type RAGFlowFormItemProps = {
|
|||||||
tooltip?: ReactNode;
|
tooltip?: ReactNode;
|
||||||
children: ReactNode | ((field: ControllerRenderProps) => ReactNode);
|
children: ReactNode | ((field: ControllerRenderProps) => ReactNode);
|
||||||
horizontal?: boolean;
|
horizontal?: boolean;
|
||||||
|
required?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RAGFlowFormItem({
|
export function RAGFlowFormItem({
|
||||||
@ -23,6 +24,7 @@ export function RAGFlowFormItem({
|
|||||||
tooltip,
|
tooltip,
|
||||||
children,
|
children,
|
||||||
horizontal = false,
|
horizontal = false,
|
||||||
|
required = false,
|
||||||
}: RAGFlowFormItemProps) {
|
}: RAGFlowFormItemProps) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
return (
|
return (
|
||||||
@ -35,7 +37,11 @@ export function RAGFlowFormItem({
|
|||||||
'flex items-center': horizontal,
|
'flex items-center': horizontal,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormLabel tooltip={tooltip} className={cn({ 'w-1/4': horizontal })}>
|
<FormLabel
|
||||||
|
required={required}
|
||||||
|
tooltip={tooltip}
|
||||||
|
className={cn({ 'w-1/4': horizontal })}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-colors-background-neutral-standard p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-bg-base p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -24,9 +24,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useDebounce } from 'ahooks';
|
import { useDebounce } from 'ahooks';
|
||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import {
|
import {
|
||||||
useGetPaginationWithRouter,
|
useGetPaginationWithRouter,
|
||||||
useHandleSearchChange,
|
useHandleSearchChange,
|
||||||
@ -80,7 +78,7 @@ export const EmptyDsl = {
|
|||||||
component_name: 'Begin',
|
component_name: 'Begin',
|
||||||
params: {},
|
params: {},
|
||||||
},
|
},
|
||||||
downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id
|
downstream: [], // other edge target is downstream, edge source is current node id
|
||||||
upstream: [], // edge source is upstream, edge target is current node id
|
upstream: [], // edge source is upstream, edge target is current node id
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -96,21 +94,11 @@ export const EmptyDsl = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchAgentTemplates = () => {
|
export const useFetchAgentTemplates = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { data } = useQuery<IFlowTemplate[]>({
|
const { data } = useQuery<IFlowTemplate[]>({
|
||||||
queryKey: [AgentApiAction.FetchAgentTemplates],
|
queryKey: [AgentApiAction.FetchAgentTemplates],
|
||||||
initialData: [],
|
initialData: [],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await agentService.listTemplates();
|
const { data } = await agentService.listTemplates();
|
||||||
if (Array.isArray(data?.data)) {
|
|
||||||
data.data.unshift({
|
|
||||||
id: uuid(),
|
|
||||||
title: t('flow.blank'),
|
|
||||||
description: t('flow.createFromNothing'),
|
|
||||||
dsl: EmptyDsl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -41,8 +41,8 @@ export interface DSL {
|
|||||||
path?: string[];
|
path?: string[];
|
||||||
answer?: any[];
|
answer?: any[];
|
||||||
graph?: IGraph;
|
graph?: IGraph;
|
||||||
messages: Message[];
|
messages?: Message[];
|
||||||
reference: IReference[];
|
reference?: IReference[];
|
||||||
globals: Record<string, any>;
|
globals: Record<string, any>;
|
||||||
retrieval: IReference[];
|
retrieval: IReference[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -934,7 +934,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
|||||||
exceptionMethod: 'Exception method',
|
exceptionMethod: 'Exception method',
|
||||||
maxRounds: 'Max reflection rounds',
|
maxRounds: 'Max reflection rounds',
|
||||||
delayEfterError: 'Delay after error',
|
delayEfterError: 'Delay after error',
|
||||||
maxRetries: 'Max retries',
|
maxRetries: 'Max reflection rounds',
|
||||||
advancedSettings: 'Advanced Settings',
|
advancedSettings: 'Advanced Settings',
|
||||||
addTools: 'Add Tools',
|
addTools: 'Add Tools',
|
||||||
sysPromptDefultValue: `
|
sysPromptDefultValue: `
|
||||||
|
|||||||
@ -892,7 +892,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
exceptionMethod: '异常处理方法',
|
exceptionMethod: '异常处理方法',
|
||||||
maxRounds: '最大反思轮数',
|
maxRounds: '最大反思轮数',
|
||||||
delayEfterError: '错误后延迟',
|
delayEfterError: '错误后延迟',
|
||||||
maxRetries: '最大重试次数',
|
maxRetries: '最大反思轮数',
|
||||||
advancedSettings: '高级设置',
|
advancedSettings: '高级设置',
|
||||||
addTools: '添加工具',
|
addTools: '添加工具',
|
||||||
sysPromptDefultValue: `
|
sysPromptDefultValue: `
|
||||||
|
|||||||
@ -57,13 +57,6 @@ const FormSchema = z.object({
|
|||||||
// )
|
// )
|
||||||
// .optional(),
|
// .optional(),
|
||||||
message_history_window_size: z.coerce.number(),
|
message_history_window_size: z.coerce.number(),
|
||||||
tools: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
component_name: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
...LlmSettingSchema,
|
...LlmSettingSchema,
|
||||||
max_retries: z.coerce.number(),
|
max_retries: z.coerce.number(),
|
||||||
delay_after_error: z.coerce.number().optional(),
|
delay_after_error: z.coerce.number().optional(),
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { get, isEmpty } from 'lodash';
|
import { get, isEmpty, omit } from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { initialAgentValues } from '../../constant';
|
import { initialAgentValues } from '../../constant';
|
||||||
|
|
||||||
|
// You need to exclude the mcp and tools fields that are not in the form,
|
||||||
|
// otherwise the form data update will reset the tools or mcp data to an array
|
||||||
|
function omitToolsAndMcp(values: Record<string, any>) {
|
||||||
|
return omit(values, ['mcp', 'tools']);
|
||||||
|
}
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
export function useValues(node?: RAGFlowNodeType) {
|
||||||
const llmId = useFetchModelId();
|
const llmId = useFetchModelId();
|
||||||
|
|
||||||
const defaultValues = useMemo(
|
const defaultValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...initialAgentValues,
|
...omitToolsAndMcp(initialAgentValues),
|
||||||
llm_id: llmId,
|
llm_id: llmId,
|
||||||
prompts: '',
|
prompts: '',
|
||||||
}),
|
}),
|
||||||
@ -24,7 +30,7 @@ export function useValues(node?: RAGFlowNodeType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...formData,
|
...omitToolsAndMcp(formData),
|
||||||
prompts: get(formData, 'prompts.0.content', ''),
|
prompts: get(formData, 'prompts.0.content', ''),
|
||||||
};
|
};
|
||||||
}, [defaultValues, node?.data?.form]);
|
}, [defaultValues, node?.data?.form]);
|
||||||
|
|||||||
@ -1,71 +1,17 @@
|
|||||||
import { useToast } from '@/components/hooks/use-toast';
|
|
||||||
import { FileMimeType, Platform } from '@/constants/common';
|
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { IGraph } from '@/interfaces/database/flow';
|
|
||||||
import { downloadJsonFile } from '@/utils/file-util';
|
import { downloadJsonFile } from '@/utils/file-util';
|
||||||
import { message } from 'antd';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildDslData } from './use-build-dsl';
|
import { useBuildDslData } from './use-build-dsl';
|
||||||
import { useSetGraphInfo } from './use-set-graph';
|
|
||||||
|
|
||||||
export const useHandleExportOrImportJsonFile = () => {
|
export const useHandleExportJsonFile = () => {
|
||||||
const { buildDslData } = useBuildDslData();
|
const { buildDslData } = useBuildDslData();
|
||||||
const {
|
|
||||||
visible: fileUploadVisible,
|
|
||||||
hideModal: hideFileUploadModal,
|
|
||||||
showModal: showFileUploadModal,
|
|
||||||
} = useSetModalState();
|
|
||||||
const setGraphInfo = useSetGraphInfo();
|
|
||||||
const { data } = useFetchAgent();
|
const { data } = useFetchAgent();
|
||||||
const { t } = useTranslation();
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const onFileUploadOk = useCallback(
|
|
||||||
async ({
|
|
||||||
fileList,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
fileList: File[];
|
|
||||||
platform: Platform;
|
|
||||||
}) => {
|
|
||||||
console.log('🚀 ~ useHandleExportOrImportJsonFile ~ platform:', platform);
|
|
||||||
if (fileList.length > 0) {
|
|
||||||
const file = fileList[0];
|
|
||||||
if (file.type !== FileMimeType.Json) {
|
|
||||||
toast({ title: t('flow.jsonUploadTypeErrorMessage') });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphStr = await file.text();
|
|
||||||
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
|
||||||
try {
|
|
||||||
const graph = JSON.parse(graphStr);
|
|
||||||
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
|
||||||
setGraphInfo(graph ?? ({} as IGraph));
|
|
||||||
hideFileUploadModal();
|
|
||||||
} else {
|
|
||||||
message.error(errorMessage);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[hideFileUploadModal, setGraphInfo, t, toast],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleExportJson = useCallback(() => {
|
const handleExportJson = useCallback(() => {
|
||||||
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
||||||
}, [buildDslData, data.title]);
|
}, [buildDslData, data.title]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fileUploadVisible,
|
|
||||||
handleExportJson,
|
handleExportJson,
|
||||||
handleImportJson: showFileUploadModal,
|
|
||||||
hideFileUploadModal,
|
|
||||||
onFileUploadOk,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import { ReactFlowProvider } from '@xyflow/react';
|
|||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
CirclePlay,
|
CirclePlay,
|
||||||
Download,
|
|
||||||
History,
|
History,
|
||||||
LaptopMinimalCheck,
|
LaptopMinimalCheck,
|
||||||
Logs,
|
Logs,
|
||||||
@ -37,7 +36,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
import AgentCanvas from './canvas';
|
import AgentCanvas from './canvas';
|
||||||
import { DropdownProvider } from './canvas/context';
|
import { DropdownProvider } from './canvas/context';
|
||||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
import { useHandleExportJsonFile } from './hooks/use-export-json';
|
||||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
||||||
import {
|
import {
|
||||||
@ -46,7 +45,6 @@ import {
|
|||||||
useWatchAgentChange,
|
useWatchAgentChange,
|
||||||
} from './hooks/use-save-graph';
|
} from './hooks/use-save-graph';
|
||||||
import { SettingDialog } from './setting-dialog';
|
import { SettingDialog } from './setting-dialog';
|
||||||
import { UploadAgentDialog } from './upload-agent-dialog';
|
|
||||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||||
import { VersionDialog } from './version-dialog';
|
import { VersionDialog } from './version-dialog';
|
||||||
|
|
||||||
@ -71,13 +69,8 @@ export default function Agent() {
|
|||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useAgentHistoryManager();
|
useAgentHistoryManager();
|
||||||
const {
|
|
||||||
handleExportJson,
|
const { handleExportJson } = useHandleExportJsonFile();
|
||||||
handleImportJson,
|
|
||||||
fileUploadVisible,
|
|
||||||
onFileUploadOk,
|
|
||||||
hideFileUploadModal,
|
|
||||||
} = useHandleExportOrImportJsonFile();
|
|
||||||
const { saveGraph, loading } = useSaveGraph();
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
||||||
const inputs = useGetBeginNodeDataInputs();
|
const inputs = useGetBeginNodeDataInputs();
|
||||||
@ -158,11 +151,6 @@ export default function Agent() {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<AgentDropdownMenuItem onClick={handleImportJson}>
|
|
||||||
<Download />
|
|
||||||
{t('flow.import')}
|
|
||||||
</AgentDropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<AgentDropdownMenuItem onClick={handleExportJson}>
|
<AgentDropdownMenuItem onClick={handleExportJson}>
|
||||||
<Upload />
|
<Upload />
|
||||||
{t('flow.export')}
|
{t('flow.export')}
|
||||||
@ -193,12 +181,6 @@ export default function Agent() {
|
|||||||
></AgentCanvas>
|
></AgentCanvas>
|
||||||
</DropdownProvider>
|
</DropdownProvider>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
{fileUploadVisible && (
|
|
||||||
<UploadAgentDialog
|
|
||||||
hideModal={hideFileUploadModal}
|
|
||||||
onOk={onFileUploadOk}
|
|
||||||
></UploadAgentDialog>
|
|
||||||
)}
|
|
||||||
{embedVisible && (
|
{embedVisible && (
|
||||||
<EmbedDialog
|
<EmbedDialog
|
||||||
visible={embedVisible}
|
visible={embedVisible}
|
||||||
|
|||||||
@ -27,9 +27,11 @@ export default function AgentTemplates() {
|
|||||||
const [selectMenuItem, setSelectMenuItem] = useState<string>(
|
const [selectMenuItem, setSelectMenuItem] = useState<string>(
|
||||||
MenuItemKey.Recommended,
|
MenuItemKey.Recommended,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTemplateList(list);
|
setTemplateList(list);
|
||||||
}, [list]);
|
}, [list]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
visible: creatingVisible,
|
visible: creatingVisible,
|
||||||
hideModal: hideCreatingModal,
|
hideModal: hideCreatingModal,
|
||||||
@ -110,10 +112,9 @@ export default function AgentTemplates() {
|
|||||||
|
|
||||||
<main className="flex-1 bg-text-title-invert/50 h-dvh">
|
<main className="flex-1 bg-text-title-invert/50 h-dvh">
|
||||||
<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-[94vh] overflow-auto px-8 pt-8">
|
<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-[94vh] overflow-auto px-8 pt-8">
|
||||||
{tempListFilter?.map((x, index) => {
|
{tempListFilter?.map((x) => {
|
||||||
return (
|
return (
|
||||||
<TemplateCard
|
<TemplateCard
|
||||||
isCreate={index === 0}
|
|
||||||
key={x.id}
|
key={x.id}
|
||||||
data={x}
|
data={x}
|
||||||
showModal={showModal}
|
showModal={showModal}
|
||||||
|
|||||||
4
web/src/pages/agents/constant.ts
Normal file
4
web/src/pages/agents/constant.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum FlowType {
|
||||||
|
Agent = 'agent',
|
||||||
|
Flow = 'flow',
|
||||||
|
}
|
||||||
@ -6,16 +6,18 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CreateAgentForm } from './create-agent-form';
|
import { CreateAgentForm, CreateAgentFormProps } from './create-agent-form';
|
||||||
|
|
||||||
|
type CreateAgentDialogProps = CreateAgentFormProps;
|
||||||
|
|
||||||
export function CreateAgentDialog({
|
export function CreateAgentDialog({
|
||||||
hideModal,
|
hideModal,
|
||||||
onOk,
|
onOk,
|
||||||
loading,
|
loading,
|
||||||
}: IModalProps<any>) {
|
shouldChooseAgent,
|
||||||
|
}: CreateAgentDialogProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,7 +26,11 @@ export function CreateAgentDialog({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t('flow.createGraph')}</DialogTitle>
|
<DialogTitle>{t('flow.createGraph')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<CreateAgentForm hideModal={hideModal} onOk={onOk}></CreateAgentForm>
|
<CreateAgentForm
|
||||||
|
hideModal={hideModal}
|
||||||
|
onOk={onOk}
|
||||||
|
shouldChooseAgent={shouldChooseAgent}
|
||||||
|
></CreateAgentForm>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<ButtonLoading type="submit" form={TagRenameId} loading={loading}>
|
<ButtonLoading type="submit" form={TagRenameId} loading={loading}>
|
||||||
{t('common.save')}
|
{t('common.save')}
|
||||||
|
|||||||
@ -4,38 +4,94 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import {
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
Form,
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
FormControl,
|
import { Form } from '@/components/ui/form';
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||||
|
import { BrainCircuit, Check, Route } from 'lucide-react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FlowType } from './constant';
|
||||||
|
import { NameFormField, NameFormSchema } from './name-form-field';
|
||||||
|
|
||||||
export function CreateAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
export type CreateAgentFormProps = IModalProps<any> & {
|
||||||
|
shouldChooseAgent?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlowTypeCardProps = {
|
||||||
|
value?: FlowType;
|
||||||
|
onChange?: (value: FlowType) => void;
|
||||||
|
};
|
||||||
|
function FlowTypeCards({ value, onChange }: FlowTypeCardProps) {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(value: FlowType) => () => {
|
||||||
|
onChange?.(value);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="flex gap-10">
|
||||||
|
{Object.values(FlowType).map((val) => {
|
||||||
|
const isActive = value === val;
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={val}
|
||||||
|
className={cn('flex-1 rounded-lg border bg-transparent', {
|
||||||
|
'border-text-primary': isActive,
|
||||||
|
'border-border-default': !isActive,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CardContent
|
||||||
|
onClick={handleChange(val)}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer p-5 text-text-secondary flex justify-between items-center',
|
||||||
|
{
|
||||||
|
'text-text-primary': isActive,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{val === FlowType.Agent ? (
|
||||||
|
<BrainCircuit className="size-6" />
|
||||||
|
) : (
|
||||||
|
<Route className="size-6" />
|
||||||
|
)}
|
||||||
|
<p>{val}</p>
|
||||||
|
</div>
|
||||||
|
{isActive && <Check />}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormSchema = z.object({
|
||||||
|
...NameFormSchema,
|
||||||
|
tag: z.string().trim().optional(),
|
||||||
|
description: z.string().trim().optional(),
|
||||||
|
type: z.nativeEnum(FlowType).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
|
export function CreateAgentForm({
|
||||||
|
hideModal,
|
||||||
|
onOk,
|
||||||
|
shouldChooseAgent = false,
|
||||||
|
}: CreateAgentFormProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const FormSchema = z.object({
|
|
||||||
name: z
|
|
||||||
.string()
|
|
||||||
.min(1, {
|
|
||||||
message: t('common.namePlaceholder'),
|
|
||||||
})
|
|
||||||
.trim(),
|
|
||||||
tag: z.string().trim().optional(),
|
|
||||||
description: z.string().trim().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<FormSchemaType>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues: { name: '' },
|
defaultValues: { name: '', type: FlowType.Agent },
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
async function onSubmit(data: FormSchemaType) {
|
||||||
const ret = await onOk?.(data);
|
const ret = await onOk?.(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
hideModal?.();
|
hideModal?.();
|
||||||
@ -49,57 +105,12 @@ export function CreateAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
|||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
id={TagRenameId}
|
id={TagRenameId}
|
||||||
>
|
>
|
||||||
<FormField
|
{shouldChooseAgent && (
|
||||||
control={form.control}
|
<RAGFlowFormItem required name="type" label={t('common.type')}>
|
||||||
name="name"
|
<FlowTypeCards></FlowTypeCards>
|
||||||
render={({ field }) => (
|
</RAGFlowFormItem>
|
||||||
<FormItem>
|
)}
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
<NameFormField></NameFormField>
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={t('common.namePlaceholder')}
|
|
||||||
{...field}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
|
||||||
name="tag"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.tag')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={t('flow.tagPlaceholder')}
|
|
||||||
{...field}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.description')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={t('flow.descriptionPlaceholder')}
|
|
||||||
{...field}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
42
web/src/pages/agents/hooks/use-create-agent.ts
Normal file
42
web/src/pages/agents/hooks/use-create-agent.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request';
|
||||||
|
import { DSL } from '@/interfaces/database/agent';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { FlowType } from '../constant';
|
||||||
|
import { FormSchemaType } from '../create-agent-form';
|
||||||
|
|
||||||
|
export function useCreateAgentOrPipeline() {
|
||||||
|
const { loading, setAgent } = useSetAgent();
|
||||||
|
const {
|
||||||
|
visible: creatingVisible,
|
||||||
|
hideModal: hideCreatingModal,
|
||||||
|
showModal: showCreatingModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const createAgent = useCallback(
|
||||||
|
async (name: string) => {
|
||||||
|
return setAgent({ title: name, dsl: EmptyDsl as DSL });
|
||||||
|
},
|
||||||
|
[setAgent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateAgentOrPipeline = useCallback(
|
||||||
|
async (data: FormSchemaType) => {
|
||||||
|
if (data.type === FlowType.Agent) {
|
||||||
|
const ret = await createAgent(data.name);
|
||||||
|
if (ret.code === 0) {
|
||||||
|
hideCreatingModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createAgent, hideCreatingModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
creatingVisible,
|
||||||
|
hideCreatingModal,
|
||||||
|
showCreatingModal,
|
||||||
|
handleCreateAgentOrPipeline,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,14 +1,24 @@
|
|||||||
import ListFilterBar from '@/components/list-filter-bar';
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { RenameDialog } from '@/components/rename-dialog';
|
import { RenameDialog } from '@/components/rename-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
|
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { Plus } from 'lucide-react';
|
import { Clipboard, ClipboardPlus, FileInput, Plus } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { AgentCard } from './agent-card';
|
import { AgentCard } from './agent-card';
|
||||||
|
import { CreateAgentDialog } from './create-agent-dialog';
|
||||||
|
import { useCreateAgentOrPipeline } from './hooks/use-create-agent';
|
||||||
|
import { UploadAgentDialog } from './upload-agent-dialog';
|
||||||
|
import { useHandleImportJsonFile } from './use-import-json';
|
||||||
import { useRenameAgent } from './use-rename-agent';
|
import { useRenameAgent } from './use-rename-agent';
|
||||||
|
|
||||||
export default function Agents() {
|
export default function Agents() {
|
||||||
@ -25,6 +35,21 @@ export default function Agents() {
|
|||||||
showAgentRenameModal,
|
showAgentRenameModal,
|
||||||
} = useRenameAgent();
|
} = useRenameAgent();
|
||||||
|
|
||||||
|
const {
|
||||||
|
creatingVisible,
|
||||||
|
hideCreatingModal,
|
||||||
|
showCreatingModal,
|
||||||
|
loading,
|
||||||
|
handleCreateAgentOrPipeline,
|
||||||
|
} = useCreateAgentOrPipeline();
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleImportJson,
|
||||||
|
fileUploadVisible,
|
||||||
|
onFileUploadOk,
|
||||||
|
hideFileUploadModal,
|
||||||
|
} = useHandleImportJsonFile();
|
||||||
|
|
||||||
const handlePageChange = useCallback(
|
const handlePageChange = useCallback(
|
||||||
(page: number, pageSize?: number) => {
|
(page: number, pageSize?: number) => {
|
||||||
setPagination({ page, pageSize });
|
setPagination({ page, pageSize });
|
||||||
@ -41,10 +66,37 @@ export default function Agents() {
|
|||||||
onSearchChange={handleInputChange}
|
onSearchChange={handleInputChange}
|
||||||
icon="agent"
|
icon="agent"
|
||||||
>
|
>
|
||||||
<Button onClick={navigateToAgentTemplates}>
|
<DropdownMenu>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<DropdownMenuTrigger>
|
||||||
{t('flow.createGraph')}
|
<Button>
|
||||||
</Button>
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
{t('flow.createGraph')}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
justifyBetween={false}
|
||||||
|
onClick={showCreatingModal}
|
||||||
|
>
|
||||||
|
<Clipboard />
|
||||||
|
Create from Blank
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
justifyBetween={false}
|
||||||
|
onClick={navigateToAgentTemplates}
|
||||||
|
>
|
||||||
|
<ClipboardPlus />
|
||||||
|
Create from Template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
justifyBetween={false}
|
||||||
|
onClick={handleImportJson}
|
||||||
|
>
|
||||||
|
<FileInput />
|
||||||
|
Import json file
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
@ -75,6 +127,21 @@ export default function Agents() {
|
|||||||
loading={agentRenameLoading}
|
loading={agentRenameLoading}
|
||||||
></RenameDialog>
|
></RenameDialog>
|
||||||
)}
|
)}
|
||||||
|
{creatingVisible && (
|
||||||
|
<CreateAgentDialog
|
||||||
|
loading={loading}
|
||||||
|
visible={creatingVisible}
|
||||||
|
hideModal={hideCreatingModal}
|
||||||
|
shouldChooseAgent
|
||||||
|
onOk={handleCreateAgentOrPipeline}
|
||||||
|
></CreateAgentDialog>
|
||||||
|
)}
|
||||||
|
{fileUploadVisible && (
|
||||||
|
<UploadAgentDialog
|
||||||
|
hideModal={hideFileUploadModal}
|
||||||
|
onOk={onFileUploadOk}
|
||||||
|
></UploadAgentDialog>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
28
web/src/pages/agents/name-form-field.tsx
Normal file
28
web/src/pages/agents/name-form-field.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import i18n from '@/locales/config';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const NameFormSchema = {
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, {
|
||||||
|
message: i18n.t('common.namePlaceholder'),
|
||||||
|
})
|
||||||
|
.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function NameFormField() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<RAGFlowFormItem
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
label={t('common.name')}
|
||||||
|
tooltip={t('flow.sqlStatementTip')}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('common.namePlaceholder')} autoComplete="off" />
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { IFlowTemplate } from '@/interfaces/database/flow';
|
import { IFlowTemplate } from '@/interfaces/database/flow';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -12,7 +11,7 @@ interface IProps {
|
|||||||
showModal(record: IFlowTemplate): void;
|
showModal(record: IFlowTemplate): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TemplateCard({ data, showModal, isCreate = false }: IProps) {
|
export function TemplateCard({ data, showModal }: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
@ -26,41 +25,24 @@ export function TemplateCard({ data, showModal, isCreate = false }: IProps) {
|
|||||||
return (
|
return (
|
||||||
<Card className="border-colors-outline-neutral-standard group relative min-h-40">
|
<Card className="border-colors-outline-neutral-standard group relative min-h-40">
|
||||||
<CardContent className="p-4 ">
|
<CardContent className="p-4 ">
|
||||||
{isCreate && (
|
<div className="flex justify-start items-center gap-4 mb-4">
|
||||||
<div
|
<RAGFlowAvatar
|
||||||
className="flex flex-col justify-center items-center gap-4 mb-4 absolute top-0 right-0 left-0 bottom-0 cursor-pointer "
|
className="w-7 h-7"
|
||||||
|
avatar={data.avatar ? data.avatar : 'https://github.com/shadcn.png'}
|
||||||
|
name={data?.title[language] || 'CN'}
|
||||||
|
></RAGFlowAvatar>
|
||||||
|
<div className="text-[18px] font-bold ">{data?.title[language]}</div>
|
||||||
|
</div>
|
||||||
|
<p className="break-words">{data?.description[language]}</p>
|
||||||
|
<div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
className="w-1/3 absolute bottom-4 right-4 left-4 justify-center text-center m-auto"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Plus size={50} fontWeight={700} />
|
{t('flow.useTemplate')}
|
||||||
<div>{t('flow.createAgent')}</div>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{!isCreate && (
|
|
||||||
<>
|
|
||||||
<div className="flex justify-start items-center gap-4 mb-4">
|
|
||||||
<RAGFlowAvatar
|
|
||||||
className="w-7 h-7"
|
|
||||||
avatar={
|
|
||||||
data.avatar ? data.avatar : 'https://github.com/shadcn.png'
|
|
||||||
}
|
|
||||||
name={data?.title[language] || 'CN'}
|
|
||||||
></RAGFlowAvatar>
|
|
||||||
<div className="text-[18px] font-bold ">
|
|
||||||
{data?.title[language]}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="break-words">{data?.description[language]}</p>
|
|
||||||
<div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl">
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
className="w-1/3 absolute bottom-4 right-4 left-4 justify-center text-center m-auto"
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
{t('flow.useTemplate')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ButtonLoading } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -5,7 +6,6 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { LoadingButton } from '@/components/ui/loading-button';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -26,9 +26,9 @@ export function UploadAgentDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<UploadAgentForm hideModal={hideModal} onOk={onOk}></UploadAgentForm>
|
<UploadAgentForm hideModal={hideModal} onOk={onOk}></UploadAgentForm>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
<ButtonLoading type="submit" form={TagRenameId} loading={loading}>
|
||||||
{t('common.save')}
|
{t('common.save')}
|
||||||
</LoadingButton>
|
</ButtonLoading>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -13,32 +13,24 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { FileMimeType, Platform } from '@/constants/common';
|
import { FileMimeType } from '@/constants/common';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { NameFormField, NameFormSchema } from '../name-form-field';
|
||||||
|
|
||||||
// const options = Object.values(Platform).map((x) => ({ label: x, value: x }));
|
export const FormSchema = z.object({
|
||||||
|
fileList: z.array(z.instanceof(File)),
|
||||||
|
...NameFormSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormSchemaType = z.infer<typeof FormSchema>;
|
||||||
export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const FormSchema = z.object({
|
|
||||||
platform: z
|
|
||||||
.string()
|
|
||||||
.min(1, {
|
|
||||||
message: t('common.namePlaceholder'),
|
|
||||||
})
|
|
||||||
.trim(),
|
|
||||||
fileList: z.array(z.instanceof(File)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues: { platform: Platform.RAGFlow },
|
defaultValues: { name: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
async function onSubmit(data: FormSchemaType) {
|
||||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
|
||||||
const ret = await onOk?.(data);
|
const ret = await onOk?.(data);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
hideModal?.();
|
hideModal?.();
|
||||||
@ -52,12 +44,13 @@ export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
|||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
id={TagRenameId}
|
id={TagRenameId}
|
||||||
>
|
>
|
||||||
|
<NameFormField></NameFormField>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="fileList"
|
name="fileList"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
<FormLabel required>DSL</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
value={field.value}
|
value={field.value}
|
||||||
@ -70,19 +63,6 @@ export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
|
||||||
name="platform"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('common.name')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect {...field} options={options} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
56
web/src/pages/agents/use-import-json.ts
Normal file
56
web/src/pages/agents/use-import-json.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { useToast } from '@/components/hooks/use-toast';
|
||||||
|
import { FileMimeType } from '@/constants/common';
|
||||||
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FormSchemaType } from './upload-agent-dialog/upload-agent-form';
|
||||||
|
|
||||||
|
export const useHandleImportJsonFile = () => {
|
||||||
|
const {
|
||||||
|
visible: fileUploadVisible,
|
||||||
|
hideModal: hideFileUploadModal,
|
||||||
|
showModal: showFileUploadModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { loading, setAgent } = useSetAgent();
|
||||||
|
|
||||||
|
const onFileUploadOk = useCallback(
|
||||||
|
async ({ fileList, name }: FormSchemaType) => {
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
const file = fileList[0];
|
||||||
|
if (file.type !== FileMimeType.Json) {
|
||||||
|
toast({ title: t('flow.jsonUploadTypeErrorMessage') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const graphStr = await file.text();
|
||||||
|
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
||||||
|
try {
|
||||||
|
const graph = JSON.parse(graphStr);
|
||||||
|
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
||||||
|
const dsl = { ...EmptyDsl, graph };
|
||||||
|
setAgent({ title: name, dsl });
|
||||||
|
hideFileUploadModal();
|
||||||
|
} else {
|
||||||
|
message.error(errorMessage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[hideFileUploadModal, setAgent, t, toast],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileUploadVisible,
|
||||||
|
handleImportJson: showFileUploadModal,
|
||||||
|
hideFileUploadModal,
|
||||||
|
onFileUploadOk,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user