mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 16:45:08 +08:00
Compare commits
6 Commits
b15643bd80
...
8123942ec1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8123942ec1 | |||
| 685114d253 | |||
| c9e56d20cf | |||
| 8ee0b6ea54 | |||
| f50b2461cb | |||
| 617faee718 |
@ -266,7 +266,7 @@ class LLMBundle(LLM4Tenant):
|
||||
break
|
||||
|
||||
if txt.endswith("</think>"):
|
||||
ans = ans.rstrip("</think>")
|
||||
ans = ans[: -len("</think>")]
|
||||
|
||||
if not self.verbose_tool_use:
|
||||
txt = re.sub(r"<tool_call>.*?</tool_call>", "", txt, flags=re.DOTALL)
|
||||
|
||||
@ -351,7 +351,7 @@ def queue_tasks(doc: dict, bucket: str, name: str, priority: int):
|
||||
"progress": 0.0,
|
||||
"from_page": 0,
|
||||
"to_page": 100000000,
|
||||
"begin_at": datetime.now(),
|
||||
"begin_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
|
||||
parse_task_array = []
|
||||
@ -503,7 +503,7 @@ def queue_dataflow(tenant_id:str, flow_id:str, task_id:str, doc_id:str=CANVAS_DE
|
||||
to_page=100000000,
|
||||
task_type="dataflow" if not rerun else "dataflow_rerun",
|
||||
priority=priority,
|
||||
begin_at=datetime.now(),
|
||||
begin_at= datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
if doc_id not in [CANVAS_DEBUG_DOC_ID, GRAPH_RAPTOR_FAKE_DOC_ID]:
|
||||
TaskService.model.delete().where(TaskService.model.doc_id == doc_id).execute()
|
||||
|
||||
@ -13,7 +13,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
# Standard library imports
|
||||
import base64
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -22,13 +27,21 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
# Typing
|
||||
from typing import List, Union, Tuple
|
||||
|
||||
# Third-party imports
|
||||
import olefile
|
||||
import fitz
|
||||
import pdfplumber
|
||||
from cachetools import LRUCache, cached
|
||||
from PIL import Image
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
# Local imports
|
||||
from api.constants import IMG_BASE64_PREFIX
|
||||
from api.db import FileType
|
||||
|
||||
@ -284,3 +297,139 @@ def read_potential_broken_pdf(blob):
|
||||
return repaired
|
||||
|
||||
return blob
|
||||
|
||||
|
||||
def _is_zip(h: bytes) -> bool:
|
||||
return h.startswith(b"PK\x03\x04") or h.startswith(b"PK\x05\x06") or h.startswith(b"PK\x07\x08")
|
||||
|
||||
def _is_pdf(h: bytes) -> bool:
|
||||
return h.startswith(b"%PDF-")
|
||||
|
||||
def _is_ole(h: bytes) -> bool:
|
||||
return h.startswith(b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1")
|
||||
|
||||
def _sha10(b: bytes) -> str:
|
||||
return hashlib.sha256(b).hexdigest()[:10]
|
||||
|
||||
def _guess_ext(b: bytes) -> str:
|
||||
h = b[:8]
|
||||
if _is_zip(h):
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(b), "r") as z:
|
||||
names = [n.lower() for n in z.namelist()]
|
||||
if any(n.startswith("word/") for n in names):
|
||||
return ".docx"
|
||||
if any(n.startswith("ppt/") for n in names):
|
||||
return ".pptx"
|
||||
if any(n.startswith("xl/") for n in names):
|
||||
return ".xlsx"
|
||||
except Exception:
|
||||
pass
|
||||
return ".zip"
|
||||
if _is_pdf(h):
|
||||
return ".pdf"
|
||||
if _is_ole(h):
|
||||
return ".doc"
|
||||
return ".bin"
|
||||
|
||||
# Try to extract the real embedded payload from OLE's Ole10Native
|
||||
def _extract_ole10native_payload(data: bytes) -> bytes:
|
||||
try:
|
||||
pos = 0
|
||||
if len(data) < 4:
|
||||
return data
|
||||
_ = int.from_bytes(data[pos:pos+4], "little")
|
||||
pos += 4
|
||||
for _ in range(3): # filename/src/tmp (NUL-terminated ANSI)
|
||||
z = data.index(b"\x00", pos)
|
||||
pos = z + 1
|
||||
pos += 4
|
||||
if pos + 4 > len(data):
|
||||
return data
|
||||
size = int.from_bytes(data[pos:pos+4], "little")
|
||||
pos += 4
|
||||
if pos + size <= len(data):
|
||||
return data[pos:pos+size]
|
||||
except Exception:
|
||||
pass
|
||||
return data
|
||||
|
||||
def extract_embed_file(target: Union[bytes, bytearray]) -> List[Tuple[str, bytes]]:
|
||||
"""
|
||||
Only extract the "first layer" of embedding, returning raw (filename, bytes).
|
||||
These bytes can be directly used for io.BytesIO and then passed to zipfile/fitz/olefile and other libraries for further parsing.
|
||||
"""
|
||||
top = bytes(target)
|
||||
|
||||
head = top[:8]
|
||||
out: List[Tuple[str, bytes]] = []
|
||||
seen = set()
|
||||
|
||||
def push(b: bytes, name_hint: str = ""):
|
||||
h10 = _sha10(b)
|
||||
if h10 in seen:
|
||||
return
|
||||
seen.add(h10)
|
||||
ext = _guess_ext(b)
|
||||
# If name_hint does not have a clear extension, use the extension guessed from the content
|
||||
if "." in name_hint:
|
||||
fname = name_hint.split("/")[-1]
|
||||
else:
|
||||
fname = f"{h10}{ext}"
|
||||
out.append((fname, b))
|
||||
|
||||
# OOXML/ZIP container (docx/xlsx/pptx)
|
||||
if _is_zip(head):
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(top), "r") as z:
|
||||
embed_dirs = ("word/embeddings/", "word/objects/", "word/activex/",
|
||||
"xl/embeddings/", "ppt/embeddings/")
|
||||
for name in z.namelist():
|
||||
low = name.lower()
|
||||
if any(low.startswith(d) for d in embed_dirs):
|
||||
try:
|
||||
b = z.read(name)
|
||||
push(b, name)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
# PDF attachments
|
||||
if _is_pdf(head):
|
||||
try:
|
||||
doc = fitz.open(stream=top, filetype="pdf")
|
||||
count = getattr(doc, "embfile_count", 0)
|
||||
for i in range(count):
|
||||
try:
|
||||
data = doc.embfile_get(i)
|
||||
if data:
|
||||
push(bytes(data), f"EmbeddedFiles[{i}]")
|
||||
except Exception:
|
||||
pass
|
||||
doc.close()
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
# Legacy OLE (.doc/.xls/.ppt)
|
||||
if _is_ole(head):
|
||||
try:
|
||||
with olefile.OleFileIO(io.BytesIO(top)) as ole:
|
||||
for entry in ole.listdir():
|
||||
p = "/".join(entry)
|
||||
try:
|
||||
data = ole.openstream(entry).read()
|
||||
except Exception:
|
||||
continue
|
||||
if not data:
|
||||
continue
|
||||
if "Ole10Native" in p or "ole10native" in p.lower():
|
||||
data = _extract_ole10native_payload(data)
|
||||
push(data, p)
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
return out
|
||||
@ -9,7 +9,7 @@ The component equipped with reasoning, tool usage, and multi-agent collaboration
|
||||
|
||||
---
|
||||
|
||||
An **Agent** component fine-tunes the LLM and sets its prompt. From v0.21.0 onwards, an **Agent** component is able to work independently and with the following capabilities:
|
||||
An **Agent** component fine-tunes the LLM and sets its prompt. From v0.20.5 onwards, an **Agent** component is able to work independently and with the following capabilities:
|
||||
|
||||
- Autonomous reasoning with reflection and adjustment based on environmental feedback.
|
||||
- Use of tools or subagents to complete tasks.
|
||||
@ -24,7 +24,7 @@ An **Agent** component is essential when you need the LLM to assist with summari
|
||||
|
||||

|
||||
|
||||
2. If your Agent involves dataset retrieval, ensure you [have properly configured your target knowledge base(s)](../../dataset/configure_knowledge_base.md).
|
||||
2. If your Agent involves dataset retrieval, ensure you [have properly configured your target dataset(s)](../../dataset/configure_knowledge_base.md).
|
||||
|
||||
## Quickstart
|
||||
|
||||
@ -113,7 +113,7 @@ Click the dropdown menu of **Model** to show the model configuration window.
|
||||
- **Model**: The chat model to use.
|
||||
- Ensure you set the chat model correctly on the **Model providers** page.
|
||||
- You can use different models for different components to increase flexibility or improve overall performance.
|
||||
- **Freedom**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
- **Creavity**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
This parameter has three options:
|
||||
- **Improvise**: Produces more creative responses.
|
||||
- **Precise**: (Default) Produces more conservative responses.
|
||||
@ -132,11 +132,12 @@ Click the dropdown menu of **Model** to show the model configuration window.
|
||||
- **Frequency penalty**: Discourages the model from repeating the same words or phrases too frequently in the generated text.
|
||||
- A higher **frequency penalty** value results in the model being more conservative in its use of repeated tokens.
|
||||
- Defaults to 0.7.
|
||||
- **Max tokens**:
|
||||
- **Max tokens**:
|
||||
This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). It is disabled by default, allowing the model to determine the number of tokens in its responses.
|
||||
|
||||
:::tip NOTE
|
||||
- It is not necessary to stick with the same model for all components. If a specific model is not performing well for a particular task, consider using a different one.
|
||||
- If you are uncertain about the mechanism behind **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**, simply choose one of the three options of **Preset configurations**.
|
||||
- If you are uncertain about the mechanism behind **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**, simply choose one of the three options of **Creavity**.
|
||||
:::
|
||||
|
||||
### System prompt
|
||||
@ -147,7 +148,7 @@ An **Agent** component relies on keys (variables) to specify its data inputs. It
|
||||
|
||||
#### Advanced usage
|
||||
|
||||
From v0.21.0 onwards, four framework-level prompt blocks are available in the **System prompt** field, enabling you to customize and *override* prompts at the framework level. Type `/` or click **(x)** to view them; they appear under the **Framework** entry in the dropdown menu.
|
||||
From v0.20.5 onwards, four framework-level prompt blocks are available in the **System prompt** field, enabling you to customize and *override* prompts at the framework level. Type `/` or click **(x)** to view them; they appear under the **Framework** entry in the dropdown menu.
|
||||
|
||||
- `task_analysis` prompt block
|
||||
- This block is responsible for analyzing tasks — either a user task or a task assigned by the lead Agent when the **Agent** component is acting as a Sub-Agent.
|
||||
|
||||
@ -42,7 +42,7 @@ Click the dropdown menu of **Model** to show the model configuration window.
|
||||
- **Model**: The chat model to use.
|
||||
- Ensure you set the chat model correctly on the **Model providers** page.
|
||||
- You can use different models for different components to increase flexibility or improve overall performance.
|
||||
- **Freedom**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
- **Creavity**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
This parameter has three options:
|
||||
- **Improvise**: Produces more creative responses.
|
||||
- **Precise**: (Default) Produces more conservative responses.
|
||||
@ -61,10 +61,12 @@ Click the dropdown menu of **Model** to show the model configuration window.
|
||||
- **Frequency penalty**: Discourages the model from repeating the same words or phrases too frequently in the generated text.
|
||||
- A higher **frequency penalty** value results in the model being more conservative in its use of repeated tokens.
|
||||
- Defaults to 0.7.
|
||||
- **Max tokens**:
|
||||
This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). It is disabled by default, allowing the model to determine the number of tokens in its responses.
|
||||
|
||||
:::tip NOTE
|
||||
- It is not necessary to stick with the same model for all components. If a specific model is not performing well for a particular task, consider using a different one.
|
||||
- If you are uncertain about the mechanism behind **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**, simply choose one of the three options of **Preset configurations**.
|
||||
- If you are uncertain about the mechanism behind **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**, simply choose one of the three options of **Creavity**.
|
||||
:::
|
||||
|
||||
### Message window size
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 30
|
||||
sidebar_position: 40
|
||||
slug: /indexer_component
|
||||
---
|
||||
|
||||
@ -87,9 +87,9 @@ RAGFlow employs a combination of weighted keyword similarity and weighted vector
|
||||
|
||||
Defaults to 0.2.
|
||||
|
||||
### Keyword similarity weight
|
||||
### Vector similarity weight
|
||||
|
||||
This parameter sets the weight of keyword similarity in the combined similarity score. The total of the two weights must equal 1.0. Its default value is 0.7, which means the weight of vector similarity in the combined search is 1 - 0.7 = 0.3.
|
||||
This parameter sets the weight of vector similarity in the composite similarity score. The total of the two weights must equal 1.0. Its default value is 0.3, which means the weight of keyword similarity in a combined search is 1 - 0.3 = 0.7.
|
||||
|
||||
### Top N
|
||||
|
||||
|
||||
80
docs/guides/agent/agent_component_reference/transformer.md
Normal file
80
docs/guides/agent/agent_component_reference/transformer.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
sidebar_position: 37
|
||||
slug: /transformer_component
|
||||
---
|
||||
|
||||
# Transformer component
|
||||
|
||||
A component that uses an LLM to extract insights from the chunks.
|
||||
|
||||
---
|
||||
|
||||
A **Transformer** component indexes chunks and configures their storage formats in the document engine. It *typically* precedes the **Indexer** in the ingestion pipeline, but you can also chain multiple **Transformer** components in sequence.
|
||||
|
||||
## Scenario
|
||||
|
||||
A **Transformer** component is essential when you need the LLM to extract new information, such as keywords, questions, metadata, and summaries, from the original chunks.
|
||||
|
||||
## Configurations
|
||||
|
||||
### Model
|
||||
|
||||
Click the dropdown menu of **Model** to show the model configuration window.
|
||||
|
||||
- **Model**: The chat model to use.
|
||||
- Ensure you set the chat model correctly on the **Model providers** page.
|
||||
- You can use different models for different components to increase flexibility or improve overall performance.
|
||||
- **Creavity**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
This parameter has three options:
|
||||
- **Improvise**: Produces more creative responses.
|
||||
- **Precise**: (Default) Produces more conservative responses.
|
||||
- **Balance**: A middle ground between **Improvise** and **Precise**.
|
||||
- **Temperature**: The randomness level of the model's output.
|
||||
Defaults to 0.1.
|
||||
- Lower values lead to more deterministic and predictable outputs.
|
||||
- Higher values lead to more creative and varied outputs.
|
||||
- A temperature of zero results in the same output for the same prompt.
|
||||
- **Top P**: Nucleus sampling.
|
||||
- Reduces the likelihood of generating repetitive or unnatural text by setting a threshold *P* and restricting the sampling to tokens with a cumulative probability exceeding *P*.
|
||||
- Defaults to 0.3.
|
||||
- **Presence penalty**: Encourages the model to include a more diverse range of tokens in the response.
|
||||
- A higher **presence penalty** value results in the model being more likely to generate tokens not yet been included in the generated text.
|
||||
- Defaults to 0.4.
|
||||
- **Frequency penalty**: Discourages the model from repeating the same words or phrases too frequently in the generated text.
|
||||
- A higher **frequency penalty** value results in the model being more conservative in its use of repeated tokens.
|
||||
- Defaults to 0.7.
|
||||
- **Max tokens**:
|
||||
This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). It is disabled by default, allowing the model to determine the number of tokens in its responses.
|
||||
|
||||
:::tip NOTE
|
||||
- It is not necessary to stick with the same model for all components. If a specific model is not performing well for a particular task, consider using a different one.
|
||||
- If you are uncertain about the mechanism behind **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**, simply choose one of the three options of **Creativity**.
|
||||
:::
|
||||
|
||||
### Result destination
|
||||
|
||||
Select the type of output to be generated by the LLM:
|
||||
|
||||
- Summary
|
||||
- Keywords
|
||||
- Questions
|
||||
- Metadata
|
||||
|
||||
### System prompt
|
||||
|
||||
Typically, you use the system prompt to describe the task for the LLM, specify how it should respond, and outline other miscellaneous requirements. We do not plan to elaborate on this topic, as it can be as extensive as prompt engineering.
|
||||
|
||||
:::tip NOTE
|
||||
The system prompt here automatically updates to match your selected **Result destination**.
|
||||
:::
|
||||
|
||||
### User prompt
|
||||
|
||||
The user-defined prompt. For example, you can type `/` or click **(x)** to insert variables of preceding components in the ingestion pipeline as the LLM's input.
|
||||
|
||||
### Output
|
||||
|
||||
The global variable name for the output of the **Transformer** component, which can be referenced by subsequent **Transformer** components in the ingestion pipeline.
|
||||
|
||||
- Default: `chunks`
|
||||
- Type: `Array<Object>`
|
||||
@ -19,7 +19,7 @@ You start an AI conversation by creating an assistant.
|
||||
|
||||
> RAGFlow offers you the flexibility of choosing a different chat model for each dialogue, while allowing you to set the default models in **System Model Settings**.
|
||||
|
||||
2. Update **Assistant settings**:
|
||||
2. Update Assistant-specific settings:
|
||||
|
||||
- **Assistant name** is the name of your chat assistant. Each assistant corresponds to a dialogue with a unique combination of datasets, prompts, hybrid search configurations, and large model settings.
|
||||
- **Empty response**:
|
||||
@ -28,12 +28,12 @@ You start an AI conversation by creating an assistant.
|
||||
- **Show quote**: This is a key feature of RAGFlow and enabled by default. RAGFlow does not work like a black box. Instead, it clearly shows the sources of information that its responses are based on.
|
||||
- Select the corresponding datasets. You can select one or multiple datasets, but ensure that they use the same embedding model, otherwise an error would occur.
|
||||
|
||||
3. Update **Prompt engine**:
|
||||
3. Update Prompt-specific settings:
|
||||
|
||||
- In **System**, you fill in the prompts for your LLM, you can also leave the default prompt as-is for the beginning.
|
||||
- **Similarity threshold** sets the similarity "bar" for each chunk of text. The default is 0.2. Text chunks with lower similarity scores are filtered out of the final response.
|
||||
- **Keyword similarity weight** is set to 0.7 by default. RAGFlow uses a hybrid score system to evaluate the relevance of different text chunks. This value sets the weight assigned to the keyword similarity component in the hybrid score.
|
||||
- If **Rerank model** is left empty, the hybrid score system uses keyword similarity and vector similarity, and the default weight assigned to the vector similarity component is 1-0.7=0.3.
|
||||
- **Vector similarity weight** is set to 0.3 by default. RAGFlow uses a hybrid score system to evaluate the relevance of different text chunks. This value sets the weight assigned to the vector similarity component in the hybrid score.
|
||||
- If **Rerank model** is left empty, the hybrid score system uses keyword similarity and vector similarity, and the default weight assigned to the keyword similarity component is 1-0.3=0.7.
|
||||
- If **Rerank model** is selected, the hybrid score system uses keyword similarity and reranker score, and the default weight assigned to the reranker score is 1-0.7=0.3.
|
||||
- **Top N** determines the *maximum* number of chunks to feed to the LLM. In other words, even if more chunks are retrieved, only the top N chunks are provided as input.
|
||||
- **Multi-turn optimization** enhances user queries using existing context in a multi-round conversation. It is enabled by default. When enabled, it will consume additional LLM tokens and significantly increase the time to generate answers.
|
||||
@ -52,10 +52,10 @@ You start an AI conversation by creating an assistant.
|
||||
- HTTP method [Converse with chat assistant](../../references/http_api_reference.md#converse-with-chat-assistant), or
|
||||
- Python method [Converse with chat assistant](../../references/python_api_reference.md#converse-with-chat-assistant).
|
||||
|
||||
4. Update **Model Setting**:
|
||||
4. Update Model-specific Settings:
|
||||
|
||||
- In **Model**: you select the chat model. Though you have selected the default chat model in **System Model Settings**, RAGFlow allows you to choose an alternative chat model for your dialogue.
|
||||
- **Freedom**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
- **Creavity**: A shortcut to **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty** settings, indicating the freedom level of the model. From **Improvise**, **Precise**, to **Balance**, each preset configuration corresponds to a unique combination of **Temperature**, **Top P**, **Presence penalty**, and **Frequency penalty**.
|
||||
This parameter has three options:
|
||||
- **Improvise**: Produces more creative responses.
|
||||
- **Precise**: (Default) Produces more conservative responses.
|
||||
|
||||
@ -29,9 +29,9 @@ In contrast, chunks created from [knowledge graph construction](./construct_know
|
||||
|
||||
This sets the bar for retrieving chunks: chunks with similarities below the threshold will be filtered out. By default, the threshold is set to 0.2. This means that only chunks with hybrid similarity score of 20 or higher will be retrieved.
|
||||
|
||||
### Keyword similarity weight
|
||||
### Vector similarity weight
|
||||
|
||||
This sets the weight of keyword similarity in the combined similarity score, whether used with vector cosine similarity or a reranking score. By default, it is set to 0.7, making the weight of the other component 0.3 (1 - 0.7).
|
||||
This sets the weight of vector similarity in the composite similarity score, whether used with vector cosine similarity or a reranking score. By default, it is set to 0.3, making the weight of the other component 0.7 (1 - 0.3).
|
||||
|
||||
### Rerank model
|
||||
|
||||
|
||||
@ -137,6 +137,7 @@ dependencies = [
|
||||
"mammoth>=1.11.0",
|
||||
"markdownify>=1.2.0",
|
||||
"captcha>=0.7.1",
|
||||
"fitz>=0.0.1.dev2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@ -15,12 +15,11 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import os
|
||||
from functools import reduce
|
||||
from io import BytesIO
|
||||
from timeit import default_timer as timer
|
||||
|
||||
from docx import Document
|
||||
from docx.image.exceptions import InvalidImageStreamError, UnexpectedEndOfFileError, UnrecognizedImageError
|
||||
from docx.opc.pkgreader import _SerializedRelationships, _SerializedRelationship
|
||||
@ -31,10 +30,11 @@ from tika import parser
|
||||
|
||||
from api.db import LLMType
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.utils.file_utils import extract_embed_file
|
||||
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.mineru_parser import MinerUParser
|
||||
from deepdoc.parser.pdf_parser import PlainParser, VisionParser
|
||||
from deepdoc.parser.mineru_parser import MinerUParser
|
||||
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
|
||||
|
||||
|
||||
@ -437,6 +437,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
Successive text will be sliced into pieces using 'delimiter'.
|
||||
Next, these successive pieces are merge into chunks whose token number is no more than 'Max token number'.
|
||||
"""
|
||||
|
||||
|
||||
is_english = lang.lower() == "english" # is_english(cks)
|
||||
parser_config = kwargs.get(
|
||||
@ -450,6 +451,27 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
res = []
|
||||
pdf_parser = None
|
||||
section_images = None
|
||||
|
||||
is_root = kwargs.get("is_root", True)
|
||||
embed_res = []
|
||||
if is_root:
|
||||
# Only extract embedded files at the root call
|
||||
embeds = []
|
||||
if binary is not None:
|
||||
embeds = extract_embed_file(binary)
|
||||
else:
|
||||
raise Exception("Embedding extraction from file path is not supported.")
|
||||
|
||||
# Recursively chunk each embedded file and collect results
|
||||
for embed_filename, embed_bytes in embeds:
|
||||
try:
|
||||
sub_res = chunk(embed_filename, binary=embed_bytes, lang=lang, callback=callback, is_root=False, **kwargs) or []
|
||||
embed_res.extend(sub_res)
|
||||
except Exception as e:
|
||||
if callback:
|
||||
callback(0.05, f"Failed to chunk embed {embed_filename}: {e}")
|
||||
continue
|
||||
|
||||
if re.search(r"\.docx$", filename, re.IGNORECASE):
|
||||
callback(0.1, "Start to parse.")
|
||||
|
||||
@ -483,10 +505,12 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
"delimiter", "\n!?。;!?"))
|
||||
|
||||
if kwargs.get("section_only", False):
|
||||
chunks.extend(embed_res)
|
||||
return chunks
|
||||
|
||||
res.extend(tokenize_chunks_with_images(chunks, doc, is_english, images))
|
||||
logging.info("naive_merge({}): {}".format(filename, timer() - st))
|
||||
res.extend(embed_res)
|
||||
return res
|
||||
|
||||
elif re.search(r"\.pdf$", filename, re.IGNORECASE):
|
||||
@ -519,6 +543,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
|
||||
res = tokenize_table(tables, doc, is_english)
|
||||
callback(0.8, "Finish parsing.")
|
||||
|
||||
elif layout_recognizer == "MinerU":
|
||||
mineru_executable = os.environ.get("MINERU_EXECUTABLE", "mineru")
|
||||
pdf_parser = MinerUParser(mineru_path=mineru_executable)
|
||||
@ -621,7 +646,6 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
callback(0.8, f"tika.parser got empty content from {filename}.")
|
||||
logging.warning(f"tika.parser got empty content from {filename}.")
|
||||
return []
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"file type not supported yet(pdf, xlsx, doc, docx, txt supported)")
|
||||
@ -638,6 +662,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
"chunk_token_num", 128)), parser_config.get(
|
||||
"delimiter", "\n!?。;!?"))
|
||||
if kwargs.get("section_only", False):
|
||||
chunks.extend(embed_res)
|
||||
return chunks
|
||||
|
||||
res.extend(tokenize_chunks_with_images(chunks, doc, is_english, images))
|
||||
@ -647,11 +672,14 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
"chunk_token_num", 128)), parser_config.get(
|
||||
"delimiter", "\n!?。;!?"))
|
||||
if kwargs.get("section_only", False):
|
||||
chunks.extend(embed_res)
|
||||
return chunks
|
||||
|
||||
res.extend(tokenize_chunks(chunks, doc, is_english, pdf_parser))
|
||||
|
||||
logging.info("naive_merge({}): {}".format(filename, timer() - st))
|
||||
if embed_res:
|
||||
res.extend(embed_res)
|
||||
return res
|
||||
|
||||
|
||||
|
||||
301
uv.lock
generated
301
uv.lock
generated
@ -31,6 +31,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload-time = "2025-07-16T16:24:52.957Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "acres"
|
||||
version = "0.5.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/ba/94b63a9af588fbf7bde25ce44d55456199654a92fb7b2337767198a824b0/acres-0.5.0.tar.gz", hash = "sha256:128b6447bf5df3b6210264feccbfa018b4ac5bd337358319aec6563f99db8f3a", size = 57750, upload-time = "2025-06-04T12:40:30.329Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/e8/806475fe4cdfd8635535d3fa11bd61d19b7cc94b61b9147ebdd2ab4cbbee/acres-0.5.0-py3-none-any.whl", hash = "sha256:fcc32b974b510897de0f041609b4234f9ff03e2e960aea088f63973fb106c772", size = 12703, upload-time = "2025-06-04T12:40:28.745Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "24.1.0"
|
||||
@ -818,6 +827,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ci-info"
|
||||
version = "0.3.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/27/938d6ef93df09c686dcee1c7334578274320e98e7bf912a6409cf2c8c3e5/ci-info-0.3.0.tar.gz", hash = "sha256:1fd50cbd401f29adffeeb18b0489e232d16ac1a7458ac6bc316deab6ae535fb0", size = 25169, upload-time = "2022-07-27T17:22:49.365Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/c3/8ac768b389d5b6dda1c3ce7992b3acd2b46401f9b71439123858b17b1a2c/ci_info-0.3.0-py3-none-any.whl", hash = "sha256:e9e05d262a6c48aa03cd904475de5ce8c4da8a5435e516631c795d0487dc9e07", size = 7764, upload-time = "2022-07-27T17:22:47.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
@ -912,6 +930,24 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/1d/62f5bf92e12335eb63517f42671ed78512d48bbc69e02a942dd7b90f03f0/compressed_rtf-1.0.7-py3-none-any.whl", hash = "sha256:b7904921d78c67a0a4b7fff9fb361a00ae2b447b6edca010ce321cd98fa0fcc0", size = 7968, upload-time = "2025-03-24T23:03:57.433Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configobj"
|
||||
version = "5.0.9"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/c4/c7f9e41bc2e5f8eeae4a08a01c91b2aea3dfab40a3e14b25e87e7db8d501/configobj-5.0.9.tar.gz", hash = "sha256:03c881bbf23aa07bccf1b837005975993c4ab4427ba57f959afdd9d1a2386848", size = 101518, upload-time = "2024-09-21T12:47:46.315Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/c4/0679472c60052c27efa612b4cd3ddd2a23e885dcdc73461781d2c802d39e/configobj-5.0.9-py2.py3-none-any.whl", hash = "sha256:1ba10c5b6ee16229c79a05047aeda2b55eb4e80d7c7d8ecf17ec1ca600c79882", size = 35615, upload-time = "2024-11-26T14:03:32.972Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "7.2.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/ac/ea19242153b5e8be412a726a70e82c7b5c1537c83f61b20995b2eda3dcd7/configparser-7.2.0.tar.gz", hash = "sha256:b629cc8ae916e3afbd36d1b3d093f34193d851e11998920fdcfc4552218b7b70", size = 51273, upload-time = "2025-03-08T16:04:09.339Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/fe/f61e7129e9e689d9e40bbf8a36fb90f04eceb477f4617c02c6a18463e81f/configparser-7.2.0-py3-none-any.whl", hash = "sha256:fee5e1f3db4156dcd0ed95bc4edfa3580475537711f67a819c966b389d09ce62", size = 17232, upload-time = "2025-03-08T16:04:07.743Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.3.2"
|
||||
@ -1471,6 +1507,19 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "etelemetry"
|
||||
version = "0.3.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "ci-info" },
|
||||
{ name = "packaging" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/27/f997c9da0e179986fadd6c8474d16743f1b3697c129c2fcd1e739cd038c2/etelemetry-0.3.1-py3-none-any.whl", hash = "sha256:a64f09bcd55cbfa5684e4d9fb6d1d6a018ab99d2ea28e638435c4c26e6814a6b" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "events"
|
||||
version = "0.5"
|
||||
@ -1659,6 +1708,25 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7", size = 16159, upload-time = "2024-06-22T15:59:12.695Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fitz"
|
||||
version = "0.0.1.dev2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "configobj" },
|
||||
{ name = "configparser" },
|
||||
{ name = "httplib2" },
|
||||
{ name = "nibabel" },
|
||||
{ name = "nipype" },
|
||||
{ name = "numpy" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pyxnat" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/28/27f27d66eb82f24e6595deb26c0a875e62431878c416e38eac515023abb2/fitz-0.0.1.dev2-py2.py3-none-any.whl", hash = "sha256:3b75083d58068d9bd51695eb2f78c9c92094cd6c8dada839e93edcddf18c0c5c", size = 20003, upload-time = "2017-02-25T23:29:54.403Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flagembedding"
|
||||
version = "1.2.10"
|
||||
@ -2693,6 +2761,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-resources"
|
||||
version = "6.5.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infinity-emb"
|
||||
version = "0.0.66"
|
||||
@ -3075,6 +3152,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "looseversion"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/7e/f13dc08e0712cc2eac8e56c7909ce2ac280dbffef2ffd87bd5277ce9d58b/looseversion-1.3.0.tar.gz", hash = "sha256:ebde65f3f6bb9531a81016c6fef3eb95a61181adc47b7f949e9c0ea47911669e", size = 8799, upload-time = "2023-07-05T16:07:51.173Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/74/d5405b9b3b12e9176dff223576d7090bc161092878f533fd0dc23dd6ae1d/looseversion-1.3.0-py2.py3-none-any.whl", hash = "sha256:781ef477b45946fc03dd4c84ea87734b21137ecda0e1e122bcb3c8d16d2a56e0", size = 8237, upload-time = "2023-07-05T16:07:49.782Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "5.3.0"
|
||||
@ -3681,6 +3767,50 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibabel"
|
||||
version = "5.3.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "importlib-resources", marker = "python_full_version < '3.12'" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/61/33036cb89f1ec1fedbc4039602345d830b27cbd8a5c7bf28c2e5b5de3ea2/nibabel-5.3.2.tar.gz", hash = "sha256:0bdca6503b1c784b446c745a4542367de7756cfba0d72143b91f9ffb78be569b", size = 4504842, upload-time = "2024-10-23T14:19:55.866Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/b2/dc384197be44e2a640bb43311850e23c2c30f3b82ce7c8cdabbf0e53045e/nibabel-5.3.2-py3-none-any.whl", hash = "sha256:52970a5a8a53b1b55249cba4d9bcfaa8cc57e3e5af35a29d7352237e8680a6f8", size = 3293839, upload-time = "2024-10-23T14:19:52.65Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nipype"
|
||||
version = "1.10.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "acres" },
|
||||
{ name = "click" },
|
||||
{ name = "etelemetry" },
|
||||
{ name = "filelock" },
|
||||
{ name = "looseversion" },
|
||||
{ name = "networkx", version = "3.4.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "networkx", version = "3.5", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "nibabel" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "prov" },
|
||||
{ name = "puremagic" },
|
||||
{ name = "pydot" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "rdflib" },
|
||||
{ name = "scipy" },
|
||||
{ name = "simplejson" },
|
||||
{ name = "traits" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/1a/7ff53f5802d37085a55d7c6df7c6ebebbc8a044930628ca21f7e661c1983/nipype-1.10.0.tar.gz", hash = "sha256:19e5d6cefa70997198f78bc665ef4d3d3cb53325b5b98a72e51aefadaf6b3e0e", size = 2919807, upload-time = "2025-03-19T23:30:07.473Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/53/c5ad0140e2e4c4d92ae45558587e26b2ebc62e39eafa30b74cb052d9375b/nipype-1.10.0-py3-none-any.whl", hash = "sha256:56ced3272e77952e330f13e28328a8fe2e8a69587ca89bc34234f7d06f8319bb", size = 3200685, upload-time = "2025-03-19T23:30:05.357Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nltk"
|
||||
version = "3.9.1"
|
||||
@ -4362,6 +4492,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size = 20475, upload-time = "2023-03-27T02:01:09.31Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathlib"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298, upload-time = "2014-09-03T15:41:57.18Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363, upload-time = "2022-05-04T13:37:20.585Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "patsy"
|
||||
version = "1.0.1"
|
||||
@ -4697,6 +4836,21 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/fa/4c3ac5527ed2e5f3577167ecd5f8180ffcdc8bdd59c9f143409c19706456/protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", size = 164772, upload-time = "2024-06-25T20:54:52.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prov"
|
||||
version = "2.1.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "networkx", version = "3.4.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "networkx", version = "3.5", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pydot" },
|
||||
{ name = "python-dateutil" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/bb/442f2e478061543c9c229f48c2d3a43cb0a77642584edecac126bc1ade99/prov-2.1.1.tar.gz", hash = "sha256:7d012b164f5bbb42e118ed9d25788ab012d09082b722bc9dd4e811a309ea57f5", size = 136802, upload-time = "2025-06-24T22:01:50.767Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/17/5703ad2380e57ecceb2700e30646ba0d856d9b90c9f33b01c68a3e298e3a/prov-2.1.1-py3-none-any.whl", hash = "sha256:04f74f9151b68f0bda68c943e111b1275207b19e197689043644a1b355a9d035", size = 425860, upload-time = "2025-06-24T22:01:49.485Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.0.0"
|
||||
@ -4756,6 +4910,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/08/9c66c269b0d417a0af9fb969535f0371b8c538633535a7a6a5ca3f9231e2/psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab", size = 1163864, upload-time = "2023-10-28T09:37:28.155Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "puremagic"
|
||||
version = "1.30"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/7f/9998706bc516bdd664ccf929a1da6c6e5ee06e48f723ce45aae7cf3ff36e/puremagic-1.30.tar.gz", hash = "sha256:f9ff7ac157d54e9cf3bff1addfd97233548e75e685282d84ae11e7ffee1614c9", size = 314785, upload-time = "2025-07-04T18:48:36.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/ed/1e347d85d05b37a8b9a039ca832e5747e1e5248d0bd66042783ef48b4a37/puremagic-1.30-py3-none-any.whl", hash = "sha256:5eeeb2dd86f335b9cfe8e205346612197af3500c6872dffebf26929f56e9d3c1", size = 43304, upload-time = "2025-07-04T18:48:34.801Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
@ -5012,6 +5175,18 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/8f/86d7931c62013a5a7ebf4e1642a87d4a6050c0f570e714f61b0df1984c62/pydivert-2.1.0-py2.py3-none-any.whl", hash = "sha256:382db488e3c37c03ec9ec94e061a0b24334d78dbaeebb7d4e4d32ce4355d9da1", size = 104718, upload-time = "2017-10-20T21:36:56.726Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydot"
|
||||
version = "4.0.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyparsing" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/35/b17cb89ff865484c6a20ef46bf9d95a5f07328292578de0b295f4a6beec2/pydot-4.0.1.tar.gz", hash = "sha256:c2148f681c4a33e08bf0e26a9e5f8e4099a82e0e2a068098f32ce86577364ad5", size = 162594, upload-time = "2025-06-17T20:09:56.454Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl", hash = "sha256:869c0efadd2708c0be1f916eb669f3d664ca684bc57ffb7ecc08e70d5e93fee6", size = 37087, upload-time = "2025-06-17T20:09:55.25Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyee"
|
||||
version = "13.0.0"
|
||||
@ -5062,6 +5237,21 @@ crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymupdf"
|
||||
version = "1.26.5"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/9a/e0a4e92a85fc17be7c54afdbb113f0ade2a8bca49856d510e28bd249e462/pymupdf-1.26.5.tar.gz", hash = "sha256:8ef335e07f648492df240f2247854d0e7c0467afb9c4dc2376ec30978ec158c3", size = 84319274, upload-time = "2025-10-10T14:04:51.826Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/3f/7fc927fd66922ce838d4c974ff9a685c5f5aba108a5d94914dc05c9371f5/pymupdf-1.26.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bfb58f07ad631e5f71ad0bd6f1ff52700f7ba7ebb4973130e81e75b721beae1", size = 23065601, upload-time = "2025-10-10T13:58:43.98Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/e2/e87e62284ba98d59f1fd4fc7542ef2ed0002525754a485fa4077b3bbddae/pymupdf-1.26.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d58599479bc471d3ae56c3d68d9160d0b7de8a3bd40221ddc3a4eaae2d281b86", size = 22412612, upload-time = "2025-10-10T13:59:04.846Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/c2/af93c6367f79e9b5435f803bde51c1dc8225f054f8238162dda80b44986d/pymupdf-1.26.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7dfea81fdd73437a6a6ce83e1fcf556faee9327a6540571e58bf04fa362bb0cd", size = 23457410, upload-time = "2025-10-10T22:45:26.355Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/5a/1292a0df4ff71fbc00dfa8c08759d17c97e1e8ea9277eb5bc5f079ca188d/pymupdf-1.26.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:caad0ffeb63dcc4a29ca40f3c68d7b78d32a932e834b0056b529cc0bdbaaffc9", size = 24064941, upload-time = "2025-10-10T13:59:48.544Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/90/87b7fdfc9cd6991a3eb69a5752f6343374c34f258c511c242f4d60791eea/pymupdf-1.26.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e24e7a7d696bd398543cc5c147869edb2026d5d5a21b7f8e35db2f20170b389e", size = 24268203, upload-time = "2025-10-10T14:00:28.791Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/99/9d4b36485538e29df0a013fb02bbf6b5b0743a428fa07515e36631c43363/pymupdf-1.26.5-cp39-abi3-win32.whl", hash = "sha256:a2a42f5911d153a47bf5c3e162a0bfe8745eb9bec3e59fbaf87617b4003d8270", size = 17130722, upload-time = "2025-10-10T14:00:51.377Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/96/fd59c1532891762ea4815e73956c532053d5e26d56969e1e5d1e4ca4b207/pymupdf-1.26.5-cp39-abi3-win_amd64.whl", hash = "sha256:39a6fb58182b27b51ea8150a0cd2e4ee7e0cf71e9d6723978f28699b42ee61ae", size = 18747258, upload-time = "2025-10-10T14:01:37.346Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymysql"
|
||||
version = "1.1.1"
|
||||
@ -5404,6 +5594,20 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyxnat"
|
||||
version = "1.6.3"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
{ name = "pathlib" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/24/c8737985e65d8adbbf51970b2a75cf54b5376d68d251159d9b7c5c9673b6/pyxnat-1.6.3.tar.gz", hash = "sha256:ddd074f35f7b35b5dccb6f713b20cf083c79d6e0d3d9cafbcaabb7c661b0cc68", size = 82466, upload-time = "2025-02-04T19:03:53.801Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/df/257c0f0af8e624daa924a3899f88e6465f162d72ada3fb0b96df9e61a2d6/pyxnat-1.6.3-py3-none-any.whl", hash = "sha256:a6d84dd24486eab9731a5de5df4fb486021b095665083c2fb1d33ac1e719d3c5", size = 95408, upload-time = "2025-02-04T19:03:51.707Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
@ -5502,6 +5706,7 @@ dependencies = [
|
||||
{ name = "elasticsearch-dsl" },
|
||||
{ name = "extract-msg" },
|
||||
{ name = "filelock" },
|
||||
{ name = "fitz" },
|
||||
{ name = "flasgger" },
|
||||
{ name = "flask" },
|
||||
{ name = "flask-cors" },
|
||||
@ -5554,6 +5759,7 @@ dependencies = [
|
||||
{ name = "pyclipper" },
|
||||
{ name = "pycryptodomex" },
|
||||
{ name = "pyicu" },
|
||||
{ name = "pymupdf" },
|
||||
{ name = "pymysql" },
|
||||
{ name = "pyodbc" },
|
||||
{ name = "pypdf" },
|
||||
@ -5662,6 +5868,7 @@ requires-dist = [
|
||||
{ name = "fastembed", marker = "(platform_machine != 'x86_64' and extra == 'full') or (sys_platform == 'darwin' and extra == 'full')", specifier = ">=0.3.6,<0.4.0" },
|
||||
{ name = "fastembed-gpu", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin' and extra == 'full'", specifier = ">=0.3.6,<0.4.0" },
|
||||
{ name = "filelock", specifier = "==3.15.4" },
|
||||
{ name = "fitz", specifier = ">=0.0.1.dev2" },
|
||||
{ name = "flagembedding", marker = "extra == 'full'", specifier = "==1.2.10" },
|
||||
{ name = "flasgger", specifier = ">=0.9.7.1,<0.10.0" },
|
||||
{ name = "flask", specifier = "==3.0.3" },
|
||||
@ -5715,6 +5922,7 @@ requires-dist = [
|
||||
{ name = "pyclipper", specifier = "==1.3.0.post5" },
|
||||
{ name = "pycryptodomex", specifier = "==3.20.0" },
|
||||
{ name = "pyicu", specifier = ">=2.15.3,<3.0.0" },
|
||||
{ name = "pymupdf", specifier = ">=1.26.5" },
|
||||
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
||||
{ name = "pyodbc", specifier = ">=5.2.0,<6.0.0" },
|
||||
{ name = "pypdf", specifier = "==6.0.0" },
|
||||
@ -5815,6 +6023,19 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/30/53f41b7b728a48da8974075f56c57200d7b11e4e9fa93be3cabf8218dc0c/ranx-0.3.20-py3-none-any.whl", hash = "sha256:e056e4d5981b0328b045868cc7064fc57a545f36009fbe9bb602295ec33335de", size = 99318, upload-time = "2024-07-01T17:40:27.095Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdflib"
|
||||
version = "7.2.1"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "isodate", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pyparsing" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/99/d2fec85e5f6bdfe4367dea143119cb4469bf48710487939df0abf7e22003/rdflib-7.2.1.tar.gz", hash = "sha256:cf9b7fa25234e8925da8b1fb09700f8349b5f0f100e785fb4260e737308292ac", size = 4873802, upload-time = "2025-09-19T02:33:36.492Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/98/7fa830bb4b9da21905683a5352aa0a01a1f3082328ae976aad341e980c23/rdflib-7.2.1-py3-none-any.whl", hash = "sha256:1a175bc1386a167a42fbfaba003bfa05c164a2a3ca3cb9c0c97f9c9638ca6ac2", size = 565423, upload-time = "2025-09-19T02:33:30.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "readability-lxml"
|
||||
version = "0.8.1"
|
||||
@ -6412,6 +6633,54 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simplejson"
|
||||
version = "3.20.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/09/2bf3761de89ea2d91bdce6cf107dcd858892d0adc22c995684878826cc6b/simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5", size = 94039, upload-time = "2025-09-26T16:27:29.283Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/33/c3277db8931f0ae9e54b9292668863365672d90fb0f632f4cf9829cb7d68/simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d", size = 75894, upload-time = "2025-09-26T16:27:30.378Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/ea/ae47b04d03c7c8a7b7b1a8b39a6e27c3bd424e52f4988d70aca6293ff5e5/simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1", size = 76116, upload-time = "2025-09-26T16:27:31.42Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/42/6c9af551e5a8d0f171d6dce3d9d1260068927f7b80f1f09834e07887c8c4/simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6", size = 138827, upload-time = "2025-09-26T16:27:32.486Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/22/5e268bbcbe9f75577491e406ec0a5536f5b2fa91a3b52031fea51cd83e1d/simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01", size = 146772, upload-time = "2025-09-26T16:27:34.036Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/b4/800f14728e2ad666f420dfdb57697ca128aeae7f991b35759c09356b829a/simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3", size = 134497, upload-time = "2025-09-26T16:27:35.211Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/b9/c54eef4226c6ac8e9a389bbe5b21fef116768f97a2dc1a683c716ffe66ef/simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda", size = 138172, upload-time = "2025-09-26T16:27:36.44Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/36/4e282f5211b34620f1b2e4b51d9ddaab5af82219b9b7b78360a33f7e5387/simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2", size = 140272, upload-time = "2025-09-26T16:27:37.605Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/b0/94ad2cf32f477c449e1f63c863d8a513e2408d651c4e58fe4b6a7434e168/simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4", size = 140468, upload-time = "2025-09-26T16:27:39.015Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/46/827731e4163be3f987cb8ee90f5d444161db8f540b5e735355faa098d9bc/simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8", size = 148700, upload-time = "2025-09-26T16:27:40.171Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/28/c32121064b1ec2fb7b5d872d9a1abda62df064d35e0160eddfa907118343/simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7", size = 141323, upload-time = "2025-09-26T16:27:41.324Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/b6/c897c54326fe86dd12d101981171a49361949f4728294f418c3b86a1af77/simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53", size = 74377, upload-time = "2025-09-26T16:27:42.533Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/87/a6e03d4d80cca99c1fee4e960f3440e2f21be9470e537970f960ca5547f1/simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476", size = 76081, upload-time = "2025-09-26T16:27:43.945Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/3e/96898c6c66d9dca3f9bd14d7487bf783b4acc77471b42f979babbb68d4ca/simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f", size = 92633, upload-time = "2025-09-26T16:27:45.028Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/a2/cd2e10b880368305d89dd540685b8bdcc136df2b3c76b5ddd72596254539/simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8", size = 75309, upload-time = "2025-09-26T16:27:46.142Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/02/290f7282eaa6ebe945d35c47e6534348af97472446951dce0d144e013f4c/simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a", size = 75308, upload-time = "2025-09-26T16:27:47.542Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/91/43695f17b69e70c4b0b03247aa47fb3989d338a70c4b726bbdc2da184160/simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51", size = 143733, upload-time = "2025-09-26T16:27:48.673Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/4b/fdcaf444ac1c3cbf1c52bf00320c499e1cf05d373a58a3731ae627ba5e2d/simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd", size = 153397, upload-time = "2025-09-26T16:27:49.89Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/83/21550f81a50cd03599f048a2d588ffb7f4c4d8064ae091511e8e5848eeaa/simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013", size = 141654, upload-time = "2025-09-26T16:27:51.168Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/54/d76c0e72ad02450a3e723b65b04f49001d0e73218ef6a220b158a64639cb/simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81", size = 144913, upload-time = "2025-09-26T16:27:52.331Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/49/976f59b42a6956d4aeb075ada16ad64448a985704bc69cd427a2245ce835/simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa", size = 144568, upload-time = "2025-09-26T16:27:53.41Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/c7/30bae30424ace8cd791ca660fed454ed9479233810fe25c3f3eab3d9dc7b/simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb", size = 146239, upload-time = "2025-09-26T16:27:54.502Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/3e/7f3b7b97351c53746e7b996fcd106986cda1954ab556fd665314756618d2/simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2", size = 154497, upload-time = "2025-09-26T16:27:55.885Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/48/7241daa91d0bf19126589f6a8dcbe8287f4ed3d734e76fd4a092708947be/simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413", size = 148069, upload-time = "2025-09-26T16:27:57.039Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/f4/ef18d2962fe53e7be5123d3784e623859eec7ed97060c9c8536c69d34836/simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961", size = 74158, upload-time = "2025-09-26T16:27:58.265Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/fd/3d1158ecdc573fdad81bf3cc78df04522bf3959758bba6597ba4c956c74d/simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c", size = 75911, upload-time = "2025-09-26T16:27:59.292Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523, upload-time = "2025-09-26T16:28:00.356Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/2b/d2413f5218fc25608739e3d63fe321dfa85c5f097aa6648dbe72513a5f12/simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259", size = 75844, upload-time = "2025-09-26T16:28:01.756Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/f1/efd09efcc1e26629e120fef59be059ce7841cc6e1f949a4db94f1ae8a918/simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8", size = 75655, upload-time = "2025-09-26T16:28:03.037Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/ec/5c6db08e42f380f005d03944be1af1a6bd501cc641175429a1cbe7fb23b9/simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9", size = 150335, upload-time = "2025-09-26T16:28:05.027Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/f5/808a907485876a9242ec67054da7cbebefe0ee1522ef1c0be3bfc90f96f6/simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868", size = 158519, upload-time = "2025-09-26T16:28:06.5Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/af/b8a158246834645ea890c36136584b0cc1c0e4b83a73b11ebd9c2a12877c/simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b", size = 148571, upload-time = "2025-09-26T16:28:07.715Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/05/ed9b2571bbf38f1a2425391f18e3ac11cb1e91482c22d644a1640dea9da7/simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e", size = 152367, upload-time = "2025-09-26T16:28:08.921Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/2c/bad68b05dd43e93f77994b920505634d31ed239418eb6a88997d06599983/simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c", size = 150205, upload-time = "2025-09-26T16:28:10.086Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/46/90c7fc878061adafcf298ce60cecdee17a027486e9dce507e87396d68255/simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970", size = 151823, upload-time = "2025-09-26T16:28:11.329Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/27/b85b03349f825ae0f5d4f780cdde0bbccd4f06c3d8433f6a3882df887481/simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e", size = 158997, upload-time = "2025-09-26T16:28:12.917Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/ad/d7f3c331fb930638420ac6d236db68e9f4c28dab9c03164c3cd0e7967e15/simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544", size = 154367, upload-time = "2025-09-26T16:28:14.393Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/46/5c67324addd40fa2966f6e886cacbbe0407c03a500db94fb8bb40333fcdf/simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54", size = 74285, upload-time = "2025-09-26T16:28:15.931Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/c9/5cc2189f4acd3a6e30ffa9775bf09b354302dbebab713ca914d7134d0f29/simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab", size = 75969, upload-time = "2025-09-26T16:28:17.017Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309, upload-time = "2025-09-26T16:29:35.312Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
@ -7017,6 +7286,38 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traits"
|
||||
version = "7.0.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/ba/33e199bfae748e802f68a857035fb003089c176897bf43e2cf38ff167740/traits-7.0.2.tar.gz", hash = "sha256:a563515809cb3911975de5a54209855f0b6fdb7ca6912a5e81de26529f70428c", size = 9534785, upload-time = "2025-01-24T20:52:59.954Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/5c/6aa6aef1472a79accd4c077cc8eccf3c3a2acc4b42ece2c48f5651f2f915/traits-7.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb59a033260dfa3aacfe484307a91f318a1fa801f5e8c8293fe22834fa4b30a7", size = 5034452, upload-time = "2025-01-24T20:55:25.02Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/0a/8387ff6f32898c334b2a96b465a8790633cec3c2270893210946d43de0d3/traits-7.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5c18d5f4aea2988b15bc10e2ac9f4eb49531d1ec380857f3046a7ba14509e4b", size = 5034825, upload-time = "2025-01-24T20:56:04.238Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/15/a04a5e1cd0c2e2979365e1ac3a674ec0f16a5af36d19809c869985e63f7a/traits-7.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11950d519b113e9a34d5a99fca112866d8c36aa8fce85edadf52995ad03de07e", size = 5110401, upload-time = "2025-01-24T20:57:19.172Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/da/58d58c3495b2bfee03975d95799d5a8ac771a2f510d579935122c02d26dc/traits-7.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50b42061cb8f34119b6b7abe703982c6fa157a2fe4e10a5b9ab9f93c340d5e3", size = 5121856, upload-time = "2025-01-24T20:57:20.949Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/74/66ed1b2511c0a457f716f6c718abf807db58c76292cbd69ecf4390519fea/traits-7.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:53fbd8a0adf42d235e6a73bd3fbb3f7190a28302d151c9a25967ff6f12b918cd", size = 5109296, upload-time = "2025-01-24T20:57:23.835Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/30/60efe8a3fe454fd7b939695d556cdee7943b1ced19fc40f9b4f2a240211c/traits-7.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b48be9fb0b9e5a733e9fa5a542b0751237822e20b52fac80b5796cc606af509", size = 5117788, upload-time = "2025-01-24T20:57:27.096Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/ef/e884bd2c05d52415acb0344ed3847f1c3835d1651a4189a17e06fa2363fa/traits-7.0.2-cp310-cp310-win32.whl", hash = "sha256:5b98600b9f40e980e0cc5b1f0ade5fb1c1f1c19d25afc2b33ea30773015eb3e5", size = 5033760, upload-time = "2025-01-24T21:01:04.683Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/71/a630ee815843e3d87484c9a0368f81eb993e862aa4cb9c20822deee7e9a3/traits-7.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:def3ab01e7d636aceda9dc6ca2abf71f2a992f9ec993c7ea200157c1ca983ae7", size = 5036225, upload-time = "2025-01-24T21:01:07.817Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/db/da628e34564a89f68d6b3ff5caee8a0a932858a4a3e1bf0d077d9f6d053c/traits-7.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33fd20c3bc29fbb1f51ddb23f63173bf59a2fdafd300e5f4790352d76e4cf68e", size = 5034488, upload-time = "2025-01-24T20:55:26.853Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/4e/d64ad9fb725ff1b943432c5df32c64abb28ad17f66e976d6ce6aaa1b54d5/traits-7.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:018d4f7cbd5e18cb34bafc915134c29aa8568bccd35d9aa9102e2af9ef66cb80", size = 5034832, upload-time = "2025-01-24T20:56:06.125Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/80/f32ade6b131c69d2a3451edfa5c9f23056c3c9889b1d7918890ff6dad273/traits-7.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa323b634abd9c7f049892d86296bc1c46bad6ad80121fefeaf12039002d58ff", size = 5119215, upload-time = "2025-01-24T20:57:31.594Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/d6/0c7c2c12a53698906e86a0076d13ee3d529a5c0a44468e89cb8a91186f22/traits-7.0.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:209bfb95c094cd315f77fc610ae65af86ec0de075be2d84e6e6290ff2f860715", size = 5130753, upload-time = "2025-01-24T20:57:34.737Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/09/070aef46f818eaab7afdada8647b303facb14d4d5f931c1fb560cfc24e1b/traits-7.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f38eee0b94f9fbab2f086200e35f835ad1563ba7e078a044cb268ce50542565", size = 5117762, upload-time = "2025-01-24T20:57:36.764Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/99/fb239d5fe1ac2931c284496995998abc72f6af0ca32cfdb70095b883fab9/traits-7.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:135dc11da393f5dec1ecaf6981f0608976354435f7be53b9e9175a9c8a118127", size = 5126325, upload-time = "2025-01-24T20:57:38.638Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/48/6c1484be7d5b322c57415c9b6d39c7419ad4ee1eb52b288ddfa3893caf31/traits-7.0.2-cp311-cp311-win32.whl", hash = "sha256:c588571d981d1254d9abf8bd2f8e449f82f31ebe8f951853290910ae2f03dc84", size = 5033773, upload-time = "2025-01-24T21:01:09.598Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/f4/d8cb863aaacfe1633d2b636647bcc70b1cd2e258e4a83e71eae995a34ed4/traits-7.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:98a880b6adab40d66ce0eda1c6f4fdcf178bb182d28d0fb71d3755c36065dd39", size = 5036235, upload-time = "2025-01-24T21:01:12.296Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/6c/9b3be8e459627267de56029a0c91e9a9c9a082353cd5b9ec1edd2f4738a5/traits-7.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bccfafbda22346f0278f010458e819f0a58a95f242f91e14014b055580a15cd8", size = 5035260, upload-time = "2025-01-24T20:55:28.536Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/0c/990486e972614dd0173ea647b80c30c30d3ad4819befa9ec94f4a8a421b6/traits-7.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9899ee203fd379fb0e07aebc178940d62d5790dc311263d5c3a577f3baf7dfa", size = 5035240, upload-time = "2025-01-24T20:56:08.856Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/7c/458041d4b345ddd351451303353acbc72a36cbc47649eedb29863a37f119/traits-7.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2938cccfea2da2fdce6cc7ec1e605c923e66610df1b223cf24a4b23ba97375de", size = 5121555, upload-time = "2025-01-24T20:57:41.688Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/f3/7736bf1bee46c6fd1c488e180236067c91490cf2aea235ed851bcf2151e2/traits-7.0.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f696c4d4d03b333e8f8beec206d80d4998ce6b4801deb74c258dbc4415f92345", size = 5135379, upload-time = "2025-01-24T20:57:45.797Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/07/e80f6663d460f80f09b443175cb8118b74ca3b7bd164f1ec5c44e1da2047/traits-7.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c49384b12ecaf39b9ab156e1c7d31960206e15071a9917596ab3c265d7bb99aa", size = 5120513, upload-time = "2025-01-24T20:57:49.354Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/8b/0716f7b8f34e1b57b39f81472460f4e02491dde02fbc114bac42cf0acd85/traits-7.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6932e5a784000368aa3948890bf55c4aba10494d4a45e9bb6c2b228644f2e67c", size = 5130509, upload-time = "2025-01-24T20:57:51.933Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/bf/e0135ce54d5604c57caad8866ac56a05265943a1b3a438277fb6ee10b0f6/traits-7.0.2-cp312-cp312-win32.whl", hash = "sha256:f434da460be8b3eb9f9f35143af116622cd313fa346c0df37b026d318c88ad29", size = 5034118, upload-time = "2025-01-24T21:01:14.04Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/2b/49423d5b269dfc095e09ecbb41b987b224f4154716d91da063cebaf963a0/traits-7.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:497463a437cb8cd4bb2ed27ae4e4491a8ed3d4d8515803476c94ce952a17af54", size = 5036464, upload-time = "2025-01-24T21:01:16.256Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transformers"
|
||||
version = "4.36.2"
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import PdfDrawer from '@/components/pdf-drawer';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import { MessageType, SharedFrom } from '@/constants/chat';
|
||||
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
|
||||
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
|
||||
import { useFetchExternalChatInfo } from '@/hooks/use-chat-request';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import i18n from '@/locales/config';
|
||||
import { MessageCircle, Minimize2, Send, X } from 'lucide-react';
|
||||
import PdfDrawer from '@/components/pdf-drawer';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -234,7 +234,7 @@ const FloatingChatWidget = () => {
|
||||
const syntheticEvent = {
|
||||
target: { value: inputValue },
|
||||
currentTarget: { value: inputValue },
|
||||
preventDefault: () => { },
|
||||
preventDefault: () => {},
|
||||
} as any;
|
||||
|
||||
handleInputChange(syntheticEvent);
|
||||
@ -311,6 +311,7 @@ const FloatingChatWidget = () => {
|
||||
className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const newIsOpen = !isOpen;
|
||||
setIsOpen(newIsOpen);
|
||||
@ -325,8 +326,9 @@ const FloatingChatWidget = () => {
|
||||
'*',
|
||||
);
|
||||
}}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
@ -352,9 +354,11 @@ const FloatingChatWidget = () => {
|
||||
className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleChat}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
@ -431,10 +435,11 @@ const FloatingChatWidget = () => {
|
||||
className={`flex ${message.role === MessageType.User ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${
|
||||
message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
>
|
||||
{message.role === MessageType.User ? (
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
@ -444,7 +449,13 @@ const FloatingChatWidget = () => {
|
||||
<FloatingChatWidgetMarkdown
|
||||
loading={false}
|
||||
content={message.content}
|
||||
reference={message.reference || { doc_aggs: [], chunks: [], total: 0 }}
|
||||
reference={
|
||||
message.reference || {
|
||||
doc_aggs: [],
|
||||
chunks: [],
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
/>
|
||||
)}
|
||||
@ -486,12 +497,13 @@ const FloatingChatWidget = () => {
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Type your message..."
|
||||
rows={1}
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||
style={{ minHeight: '44px', maxHeight: '120px' }}
|
||||
disabled={hasError || sendLoading}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSendMessage}
|
||||
disabled={!inputValue.trim() || sendLoading}
|
||||
className="p-3 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
@ -512,7 +524,7 @@ const FloatingChatWidget = () => {
|
||||
/>
|
||||
</>
|
||||
);
|
||||
} // Full mode - render everything together (original behavior)
|
||||
} // Full mode - render everything together (original behavior)
|
||||
return (
|
||||
<div
|
||||
className={`transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
@ -520,8 +532,9 @@ const FloatingChatWidget = () => {
|
||||
{/* Chat Widget Container */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`fixed bottom-24 right-6 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out ${isMinimized ? 'h-16' : 'h-[500px]'
|
||||
} w-[380px] overflow-hidden`}
|
||||
className={`fixed bottom-24 right-6 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out ${
|
||||
isMinimized ? 'h-16' : 'h-[500px]'
|
||||
} w-[380px] overflow-hidden`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-t-2xl">
|
||||
@ -540,12 +553,14 @@ const FloatingChatWidget = () => {
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={minimizeChat}
|
||||
className="p-1.5 hover:bg-white hover:bg-opacity-20 rounded-full transition-colors"
|
||||
>
|
||||
<Minimize2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleChat}
|
||||
className="p-1.5 hover:bg-white hover:bg-opacity-20 rounded-full transition-colors"
|
||||
>
|
||||
@ -592,10 +607,11 @@ const FloatingChatWidget = () => {
|
||||
className={`flex ${message.role === MessageType.User ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${
|
||||
message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
>
|
||||
{message.role === MessageType.User ? (
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
@ -605,7 +621,13 @@ const FloatingChatWidget = () => {
|
||||
<FloatingChatWidgetMarkdown
|
||||
loading={false}
|
||||
content={message.content}
|
||||
reference={message.reference || { doc_aggs: [], chunks: [], total: 0 }}
|
||||
reference={
|
||||
message.reference || {
|
||||
doc_aggs: [],
|
||||
chunks: [],
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
/>
|
||||
)}
|
||||
@ -650,7 +672,7 @@ const FloatingChatWidget = () => {
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Type your message..."
|
||||
rows={1}
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-black"
|
||||
style={{ minHeight: '44px', maxHeight: '120px' }}
|
||||
disabled={hasError || sendLoading}
|
||||
/>
|
||||
@ -672,9 +694,11 @@ const FloatingChatWidget = () => {
|
||||
{/* Floating Button */}
|
||||
<div className="fixed bottom-6 right-6 z-50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleChat}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
export const enum ParseDocumentType {
|
||||
DeepDOC = 'DeepDOC',
|
||||
PlainText = 'Plain Text',
|
||||
MinerU = 'MinerU',
|
||||
}
|
||||
|
||||
export function LayoutRecognizeFormField({
|
||||
@ -38,9 +39,12 @@ export function LayoutRecognizeFormField({
|
||||
const options = useMemo(() => {
|
||||
const list = optionsWithoutLLM
|
||||
? optionsWithoutLLM
|
||||
: [ParseDocumentType.DeepDOC, ParseDocumentType.PlainText].map((x) => ({
|
||||
label:
|
||||
x === ParseDocumentType.PlainText ? t(camelCase(x)) : 'DeepDoc',
|
||||
: [
|
||||
ParseDocumentType.DeepDOC,
|
||||
ParseDocumentType.PlainText,
|
||||
ParseDocumentType.MinerU,
|
||||
].map((x) => ({
|
||||
label: x === ParseDocumentType.PlainText ? t(camelCase(x)) : x,
|
||||
value: x,
|
||||
}));
|
||||
|
||||
|
||||
@ -116,7 +116,10 @@ const Modal: ModalType = ({
|
||||
type="button"
|
||||
disabled={confirmLoading || disabled}
|
||||
onClick={() => handleOk()}
|
||||
className="px-2 py-1 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
||||
className={cn(
|
||||
'px-2 py-1 bg-primary text-primary-foreground rounded-md hover:bg-primary/90',
|
||||
{ 'cursor-not-allowed': disabled },
|
||||
)}
|
||||
>
|
||||
{confirmLoading && (
|
||||
<Loader className="inline-block mr-2 h-4 w-4 animate-spin" />
|
||||
|
||||
@ -53,6 +53,10 @@ export enum AgentCategory {
|
||||
DataflowCanvas = 'dataflow_canvas',
|
||||
}
|
||||
|
||||
export enum AgentQuery {
|
||||
Category = 'category',
|
||||
}
|
||||
|
||||
export enum DataflowOperator {
|
||||
Begin = 'File',
|
||||
Note = 'Note',
|
||||
@ -62,3 +66,55 @@ export enum DataflowOperator {
|
||||
HierarchicalMerger = 'HierarchicalMerger',
|
||||
Extractor = 'Extractor',
|
||||
}
|
||||
|
||||
export enum Operator {
|
||||
Begin = 'Begin',
|
||||
Retrieval = 'Retrieval',
|
||||
Categorize = 'Categorize',
|
||||
Message = 'Message',
|
||||
Relevant = 'Relevant',
|
||||
RewriteQuestion = 'RewriteQuestion',
|
||||
KeywordExtract = 'KeywordExtract',
|
||||
Baidu = 'Baidu',
|
||||
DuckDuckGo = 'DuckDuckGo',
|
||||
Wikipedia = 'Wikipedia',
|
||||
PubMed = 'PubMed',
|
||||
ArXiv = 'ArXiv',
|
||||
Google = 'Google',
|
||||
Bing = 'Bing',
|
||||
GoogleScholar = 'GoogleScholar',
|
||||
DeepL = 'DeepL',
|
||||
GitHub = 'GitHub',
|
||||
BaiduFanyi = 'BaiduFanyi',
|
||||
QWeather = 'QWeather',
|
||||
ExeSQL = 'ExeSQL',
|
||||
Switch = 'Switch',
|
||||
WenCai = 'WenCai',
|
||||
AkShare = 'AkShare',
|
||||
YahooFinance = 'YahooFinance',
|
||||
Jin10 = 'Jin10',
|
||||
TuShare = 'TuShare',
|
||||
Note = 'Note',
|
||||
Crawler = 'Crawler',
|
||||
Invoke = 'Invoke',
|
||||
Email = 'Email',
|
||||
Iteration = 'Iteration',
|
||||
IterationStart = 'IterationItem',
|
||||
Code = 'CodeExec',
|
||||
WaitingDialogue = 'WaitingDialogue',
|
||||
Agent = 'Agent',
|
||||
Tool = 'Tool',
|
||||
TavilySearch = 'TavilySearch',
|
||||
TavilyExtract = 'TavilyExtract',
|
||||
UserFillUp = 'UserFillUp',
|
||||
StringTransform = 'StringTransform',
|
||||
SearXNG = 'SearXNG',
|
||||
Placeholder = 'Placeholder',
|
||||
File = 'File', // pipeline
|
||||
Parser = 'Parser',
|
||||
Tokenizer = 'Tokenizer',
|
||||
Splitter = 'Splitter',
|
||||
HierarchicalMerger = 'HierarchicalMerger',
|
||||
Extractor = 'Extractor',
|
||||
Generate = 'Generate',
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AgentCategory, AgentQuery } from '@/constants/agent';
|
||||
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback } from 'react';
|
||||
@ -70,8 +71,8 @@ export const useNavigatePage = () => {
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToAgent = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Agent}/${id}`);
|
||||
(id: string, category?: AgentCategory) => () => {
|
||||
navigate(`${Routes.Agent}/${id}?${AgentQuery.Category}=${category}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
@ -56,19 +56,24 @@ import { RagNode } from './node';
|
||||
import { AgentNode } from './node/agent-node';
|
||||
import { BeginNode } from './node/begin-node';
|
||||
import { CategorizeNode } from './node/categorize-node';
|
||||
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||
import { NextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||
import { ExtractorNode } from './node/extractor-node';
|
||||
import { FileNode } from './node/file-node';
|
||||
import { GenerateNode } from './node/generate-node';
|
||||
import { InvokeNode } from './node/invoke-node';
|
||||
import { IterationNode, IterationStartNode } from './node/iteration-node';
|
||||
import { KeywordNode } from './node/keyword-node';
|
||||
import { MessageNode } from './node/message-node';
|
||||
import NoteNode from './node/note-node';
|
||||
import ParserNode from './node/parser-node';
|
||||
import { PlaceholderNode } from './node/placeholder-node';
|
||||
import { RelevantNode } from './node/relevant-node';
|
||||
import { RetrievalNode } from './node/retrieval-node';
|
||||
import { RewriteNode } from './node/rewrite-node';
|
||||
import { SplitterNode } from './node/splitter-node';
|
||||
import { SwitchNode } from './node/switch-node';
|
||||
import { TemplateNode } from './node/template-node';
|
||||
import TokenizerNode from './node/tokenizer-node';
|
||||
import { ToolNode } from './node/tool-node';
|
||||
|
||||
export const nodeTypes: NodeTypes = {
|
||||
@ -91,6 +96,11 @@ export const nodeTypes: NodeTypes = {
|
||||
iterationStartNode: IterationStartNode,
|
||||
agentNode: AgentNode,
|
||||
toolNode: ToolNode,
|
||||
fileNode: FileNode,
|
||||
parserNode: ParserNode,
|
||||
tokenizerNode: TokenizerNode,
|
||||
splitterNode: SplitterNode,
|
||||
contextNode: ExtractorNode,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
@ -194,6 +204,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
getConnectionStartContext,
|
||||
shouldPreventClose,
|
||||
onMove,
|
||||
nodeId,
|
||||
} = useConnectionDrag(
|
||||
reactFlowInstance,
|
||||
originalOnConnect,
|
||||
@ -312,7 +323,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
}
|
||||
}
|
||||
>
|
||||
<InnerNextStepDropdown
|
||||
<NextStepDropdown
|
||||
hideModal={() => {
|
||||
removePlaceholderNode();
|
||||
hideModal();
|
||||
@ -320,9 +331,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
}}
|
||||
position={dropdownPosition}
|
||||
onNodeCreated={onNodeCreated}
|
||||
nodeId={nodeId}
|
||||
>
|
||||
<span></span>
|
||||
</InnerNextStepDropdown>
|
||||
</NextStepDropdown>
|
||||
</HandleContext.Provider>
|
||||
)}
|
||||
</AgentInstanceContext.Provider>
|
||||
|
||||
@ -17,6 +17,9 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export function CardWithForm() {
|
||||
return (
|
||||
<Card className="w-[350px]">
|
||||
@ -55,3 +58,13 @@ export function CardWithForm() {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
type LabelCardProps = {
|
||||
className?: string;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export function LabelCard({ children, className }: LabelCardProps) {
|
||||
return (
|
||||
<div className={cn('bg-bg-card rounded-sm p-1', className)}>{children}</div>
|
||||
);
|
||||
}
|
||||
|
||||
196
web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx
Normal file
196
web/src/pages/agent/canvas/node/dropdown/accordion-operators.tsx
Normal file
@ -0,0 +1,196 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import { Operator } from '@/constants/agent';
|
||||
import useGraphStore from '@/pages/agent/store';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { OperatorItemList } from './operator-item-list';
|
||||
|
||||
export function AccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: {
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="px-2 text-text-title max-h-[45vh] overflow-auto scrollbar-none"
|
||||
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.foundation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Agent, Operator.Retrieval]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dialog')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Message, Operator.UserFillUp]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.flow')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.Switch,
|
||||
Operator.Iteration,
|
||||
Operator.Categorize,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-4">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dataManipulation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Code, Operator.StringTransform]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-5">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.tools')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.TavilySearch,
|
||||
Operator.TavilyExtract,
|
||||
Operator.ExeSQL,
|
||||
Operator.Google,
|
||||
Operator.YahooFinance,
|
||||
Operator.Email,
|
||||
Operator.DuckDuckGo,
|
||||
Operator.Wikipedia,
|
||||
Operator.GoogleScholar,
|
||||
Operator.ArXiv,
|
||||
Operator.PubMed,
|
||||
Operator.GitHub,
|
||||
Operator.Invoke,
|
||||
Operator.WenCai,
|
||||
Operator.SearXNG,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
// Limit the number of operators of a certain type on the canvas to only one
|
||||
function useRestrictSingleOperatorOnCanvas() {
|
||||
const { findNodeByName } = useGraphStore((state) => state);
|
||||
|
||||
const restrictSingleOperatorOnCanvas = useCallback(
|
||||
(singleOperators: Operator[]) => {
|
||||
const list: Operator[] = [];
|
||||
singleOperators.forEach((operator) => {
|
||||
if (!findNodeByName(operator)) {
|
||||
list.push(operator);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
},
|
||||
[findNodeByName],
|
||||
);
|
||||
|
||||
return restrictSingleOperatorOnCanvas;
|
||||
}
|
||||
|
||||
export function PipelineAccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
nodeId,
|
||||
}: {
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
nodeId?: string;
|
||||
}) {
|
||||
const restrictSingleOperatorOnCanvas = useRestrictSingleOperatorOnCanvas();
|
||||
const { getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||
|
||||
const operators = useMemo(() => {
|
||||
let list = [
|
||||
...restrictSingleOperatorOnCanvas([Operator.Parser, Operator.Tokenizer]),
|
||||
];
|
||||
list.push(Operator.Extractor);
|
||||
return list;
|
||||
}, [restrictSingleOperatorOnCanvas]);
|
||||
|
||||
const chunkerOperators = useMemo(() => {
|
||||
return [
|
||||
...restrictSingleOperatorOnCanvas([
|
||||
Operator.Splitter,
|
||||
Operator.HierarchicalMerger,
|
||||
]),
|
||||
];
|
||||
}, [restrictSingleOperatorOnCanvas]);
|
||||
|
||||
const showChunker = useMemo(() => {
|
||||
return (
|
||||
getOperatorTypeFromId(nodeId) !== Operator.Extractor &&
|
||||
chunkerOperators.length > 0
|
||||
);
|
||||
}, [chunkerOperators.length, getOperatorTypeFromId, nodeId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OperatorItemList
|
||||
operators={operators}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
{showChunker && (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full px-4"
|
||||
defaultValue="item-1"
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Chunker</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={chunkerOperators}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,230 +1,33 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { Operator } from '@/pages/agent/constant';
|
||||
import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
|
||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { useIsPipeline } from '@/pages/agent/hooks/use-is-pipeline';
|
||||
import { t } from 'i18next';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { PropsWithChildren, memo, useEffect, useRef } from 'react';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
createContext,
|
||||
memo,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type OperatorItemProps = {
|
||||
operators: Operator[];
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
};
|
||||
|
||||
const HideModalContext = createContext<IModalProps<any>['showModal']>(() => {});
|
||||
const OnNodeCreatedContext = createContext<
|
||||
((newNodeId: string) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
function OperatorItemList({
|
||||
operators,
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: OperatorItemProps) {
|
||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||
const handleContext = useContext(HandleContext);
|
||||
const hideModal = useContext(HideModalContext);
|
||||
const onNodeCreated = useContext(OnNodeCreatedContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick =
|
||||
(operator: Operator): React.MouseEventHandler<HTMLElement> =>
|
||||
(e) => {
|
||||
const contextData = handleContext || {
|
||||
nodeId: '',
|
||||
id: '',
|
||||
type: 'source' as const,
|
||||
position: Position.Right,
|
||||
isFromConnectionDrag: true,
|
||||
};
|
||||
|
||||
const mockEvent = mousePosition
|
||||
? {
|
||||
clientX: mousePosition.x,
|
||||
clientY: mousePosition.y,
|
||||
}
|
||||
: e;
|
||||
|
||||
const newNodeId = addCanvasNode(operator, contextData)(mockEvent);
|
||||
|
||||
if (onNodeCreated && newNodeId) {
|
||||
onNodeCreated(newNodeId);
|
||||
}
|
||||
|
||||
hideModal?.();
|
||||
};
|
||||
|
||||
const renderOperatorItem = (operator: Operator) => {
|
||||
const commonContent = (
|
||||
<div className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start">
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip key={operator}>
|
||||
<TooltipTrigger asChild>
|
||||
{isCustomDropdown ? (
|
||||
<li onClick={handleClick(operator)}>{commonContent}</li>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={operator}
|
||||
className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
onClick={handleClick(operator)}
|
||||
onSelect={() => hideModal?.()}
|
||||
>
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={24}>
|
||||
<p>{t(`flow.${lowerFirst(operator)}Description`)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return <ul className="space-y-2">{operators.map(renderOperatorItem)}</ul>;
|
||||
}
|
||||
|
||||
function AccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: {
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
}) {
|
||||
return (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="px-2 text-text-title max-h-[45vh] overflow-auto scrollbar-none"
|
||||
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.foundation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Agent, Operator.Retrieval]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dialog')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Message, Operator.UserFillUp]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.flow')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.Switch,
|
||||
Operator.Iteration,
|
||||
Operator.Categorize,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-4">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dataManipulation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Code, Operator.StringTransform]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-5">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.tools')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.TavilySearch,
|
||||
Operator.TavilyExtract,
|
||||
Operator.ExeSQL,
|
||||
Operator.Google,
|
||||
Operator.YahooFinance,
|
||||
Operator.Email,
|
||||
Operator.DuckDuckGo,
|
||||
Operator.Wikipedia,
|
||||
Operator.GoogleScholar,
|
||||
Operator.ArXiv,
|
||||
Operator.PubMed,
|
||||
Operator.GitHub,
|
||||
Operator.Invoke,
|
||||
Operator.WenCai,
|
||||
Operator.SearXNG,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
AccordionOperators,
|
||||
PipelineAccordionOperators,
|
||||
} from './accordion-operators';
|
||||
import { HideModalContext, OnNodeCreatedContext } from './operator-item-list';
|
||||
|
||||
export function InnerNextStepDropdown({
|
||||
children,
|
||||
hideModal,
|
||||
position,
|
||||
onNodeCreated,
|
||||
nodeId,
|
||||
}: PropsWithChildren &
|
||||
IModalProps<any> & {
|
||||
position?: { x: number; y: number };
|
||||
onNodeCreated?: (newNodeId: string) => void;
|
||||
nodeId?: string;
|
||||
}) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const isPipeline = useIsPipeline();
|
||||
|
||||
useEffect(() => {
|
||||
if (position && hideModal) {
|
||||
@ -260,10 +63,18 @@ export function InnerNextStepDropdown({
|
||||
</div>
|
||||
<HideModalContext.Provider value={hideModal}>
|
||||
<OnNodeCreatedContext.Provider value={onNodeCreated}>
|
||||
<AccordionOperators
|
||||
isCustomDropdown={true}
|
||||
mousePosition={position}
|
||||
></AccordionOperators>
|
||||
{isPipeline ? (
|
||||
<PipelineAccordionOperators
|
||||
isCustomDropdown={true}
|
||||
mousePosition={position}
|
||||
nodeId={nodeId}
|
||||
></PipelineAccordionOperators>
|
||||
) : (
|
||||
<AccordionOperators
|
||||
isCustomDropdown={true}
|
||||
mousePosition={position}
|
||||
></AccordionOperators>
|
||||
)}
|
||||
</OnNodeCreatedContext.Provider>
|
||||
</HideModalContext.Provider>
|
||||
</div>
|
||||
@ -287,7 +98,11 @@ export function InnerNextStepDropdown({
|
||||
>
|
||||
<DropdownMenuLabel>{t('flow.nextStep')}</DropdownMenuLabel>
|
||||
<HideModalContext.Provider value={hideModal}>
|
||||
<AccordionOperators></AccordionOperators>
|
||||
{isPipeline ? (
|
||||
<PipelineAccordionOperators></PipelineAccordionOperators>
|
||||
) : (
|
||||
<AccordionOperators></AccordionOperators>
|
||||
)}
|
||||
</HideModalContext.Provider>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
100
web/src/pages/agent/canvas/node/dropdown/operator-item-list.tsx
Normal file
100
web/src/pages/agent/canvas/node/dropdown/operator-item-list.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Operator } from '@/constants/agent';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
|
||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { createContext, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type OperatorItemProps = {
|
||||
operators: Operator[];
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
};
|
||||
|
||||
export const HideModalContext = createContext<IModalProps<any>['showModal']>(
|
||||
() => {},
|
||||
);
|
||||
export const OnNodeCreatedContext = createContext<
|
||||
((newNodeId: string) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
export function OperatorItemList({
|
||||
operators,
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: OperatorItemProps) {
|
||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||
const handleContext = useContext(HandleContext);
|
||||
const hideModal = useContext(HideModalContext);
|
||||
const onNodeCreated = useContext(OnNodeCreatedContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick =
|
||||
(operator: Operator): React.MouseEventHandler<HTMLElement> =>
|
||||
(e) => {
|
||||
const contextData = handleContext || {
|
||||
nodeId: '',
|
||||
id: '',
|
||||
type: 'source' as const,
|
||||
position: Position.Right,
|
||||
isFromConnectionDrag: true,
|
||||
};
|
||||
|
||||
const mockEvent = mousePosition
|
||||
? {
|
||||
clientX: mousePosition.x,
|
||||
clientY: mousePosition.y,
|
||||
}
|
||||
: e;
|
||||
|
||||
const newNodeId = addCanvasNode(operator, contextData)(mockEvent);
|
||||
|
||||
if (onNodeCreated && newNodeId) {
|
||||
onNodeCreated(newNodeId);
|
||||
}
|
||||
|
||||
hideModal?.();
|
||||
};
|
||||
|
||||
const renderOperatorItem = (operator: Operator) => {
|
||||
const commonContent = (
|
||||
<div className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start">
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip key={operator}>
|
||||
<TooltipTrigger asChild>
|
||||
{isCustomDropdown ? (
|
||||
<li onClick={handleClick(operator)}>{commonContent}</li>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={operator}
|
||||
className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
onClick={handleClick(operator)}
|
||||
onSelect={() => hideModal?.()}
|
||||
>
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={24}>
|
||||
<p>{t(`flow.${lowerFirst(operator)}Description`)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return <ul className="space-y-2">{operators.map(renderOperatorItem)}</ul>;
|
||||
}
|
||||
18
web/src/pages/agent/canvas/node/extractor-node.tsx
Normal file
18
web/src/pages/agent/canvas/node/extractor-node.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { IRagNode } from '@/interfaces/database/agent';
|
||||
import { NodeProps } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { LabelCard } from './card';
|
||||
import { RagNode } from './index';
|
||||
|
||||
export function ExtractorNode({ ...props }: NodeProps<IRagNode>) {
|
||||
const { data } = props;
|
||||
|
||||
return (
|
||||
<RagNode {...props}>
|
||||
<LabelCard>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</LabelCard>
|
||||
</RagNode>
|
||||
);
|
||||
}
|
||||
62
web/src/pages/agent/canvas/node/file-node.tsx
Normal file
62
web/src/pages/agent/canvas/node/file-node.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { IBeginNode } from '@/interfaces/database/flow';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import get from 'lodash/get';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BeginQueryType,
|
||||
BeginQueryTypeIconMap,
|
||||
NodeHandleId,
|
||||
Operator,
|
||||
} from '../../constant';
|
||||
import { BeginQuery } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
|
||||
// TODO: do not allow other nodes to connect to this node
|
||||
function InnerFileNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||
const { t } = useTranslation();
|
||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||
|
||||
return (
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
id={NodeHandleId.Start}
|
||||
></CommonHandle>
|
||||
|
||||
<section className="flex items-center gap-2">
|
||||
<OperatorIcon name={data.label as Operator}></OperatorIcon>
|
||||
<div className="truncate text-center font-semibold text-sm">
|
||||
{t(`dataflow.begin`)}
|
||||
</div>
|
||||
</section>
|
||||
<section className={cn(styles.generateParameters, 'flex gap-2 flex-col')}>
|
||||
{Object.entries(inputs).map(([key, val], idx) => {
|
||||
const Icon = BeginQueryTypeIconMap[val.type as BeginQueryType];
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={cn(styles.conditionBlock, 'flex gap-1.5 items-center')}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
<label htmlFor="">{key}</label>
|
||||
<span className={styles.parameterValue}>{val.name}</span>
|
||||
<span className="flex-1">{val.optional ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const FileNode = memo(InnerFileNode);
|
||||
@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { HandleContext } from '../../context';
|
||||
import { useDropdownManager } from '../context';
|
||||
import { InnerNextStepDropdown } from './dropdown/next-step-dropdown';
|
||||
import { NextStepDropdown } from './dropdown/next-step-dropdown';
|
||||
|
||||
export function CommonHandle({
|
||||
className,
|
||||
@ -50,14 +50,15 @@ export function CommonHandle({
|
||||
>
|
||||
<Plus className="size-3 pointer-events-none text-white hidden group-hover:inline-block" />
|
||||
{visible && (
|
||||
<InnerNextStepDropdown
|
||||
<NextStepDropdown
|
||||
nodeId={nodeId}
|
||||
hideModal={() => {
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
</InnerNextStepDropdown>
|
||||
</NextStepDropdown>
|
||||
)}
|
||||
</Handle>
|
||||
</HandleContext.Provider>
|
||||
|
||||
@ -2,7 +2,7 @@ import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import { needsSingleStepDebugging, showCopyIcon } from '../../utils';
|
||||
import { CommonHandle, LeftEndHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
@ -21,6 +21,7 @@ function InnerRagNode({
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={needsSingleStepDebugging(data.label)}
|
||||
showCopy={showCopyIcon(data.label)}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<LeftEndHandle></LeftEndHandle>
|
||||
|
||||
@ -9,6 +9,7 @@ interface IProps {
|
||||
gap?: number;
|
||||
className?: string;
|
||||
wrapperClassName?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const InnerNodeHeader = ({
|
||||
@ -16,11 +17,12 @@ const InnerNodeHeader = ({
|
||||
name,
|
||||
className,
|
||||
wrapperClassName,
|
||||
icon,
|
||||
}: IProps) => {
|
||||
return (
|
||||
<section className={cn(wrapperClassName, 'pb-2')}>
|
||||
<div className={cn(className, 'flex gap-2.5')}>
|
||||
<OperatorIcon name={label as Operator}></OperatorIcon>
|
||||
{icon || <OperatorIcon name={label as Operator}></OperatorIcon>}
|
||||
<span className="truncate text-center font-semibold text-sm">
|
||||
{name}
|
||||
</span>
|
||||
|
||||
57
web/src/pages/agent/canvas/node/parser-node.tsx
Normal file
57
web/src/pages/agent/canvas/node/parser-node.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { NodeCollapsible } from '@/components/collapse';
|
||||
import { BaseNode } from '@/interfaces/database/agent';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { ParserFormSchemaType } from '../../form/parser-form';
|
||||
import { LabelCard } from './card';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
|
||||
function ParserNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<BaseNode<ParserFormSchemaType>>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
|
||||
<NodeCollapsible items={data.form?.setups}>
|
||||
{(x, idx) => (
|
||||
<LabelCard
|
||||
key={idx}
|
||||
className="flex flex-col text-text-primary gap-1"
|
||||
>
|
||||
<span className="text-text-secondary">Parser {idx + 1}</span>
|
||||
{t(`dataflow.fileFormatOptions.${x.fileFormat}`)}
|
||||
</LabelCard>
|
||||
)}
|
||||
</NodeCollapsible>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ParserNode);
|
||||
58
web/src/pages/agent/canvas/node/splitter-node.tsx
Normal file
58
web/src/pages/agent/canvas/node/splitter-node.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { LabelCard } from './card';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
type RagNodeProps = NodeProps<IRagNode> & PropsWithChildren;
|
||||
function InnerSplitterNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: RagNodeProps) {
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showCopy={false}
|
||||
showRun={false}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={'Chunker'}
|
||||
label={data.label}
|
||||
icon={<OperatorIcon name={Operator.Splitter}></OperatorIcon>}
|
||||
></NodeHeader>
|
||||
<LabelCard>{data.name}</LabelCard>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const SplitterNode = memo(InnerSplitterNode);
|
||||
55
web/src/pages/agent/canvas/node/tokenizer-node.tsx
Normal file
55
web/src/pages/agent/canvas/node/tokenizer-node.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { BaseNode } from '@/interfaces/database/agent';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { TokenizerFormSchemaType } from '../../form/tokenizer-form';
|
||||
import { LabelCard } from './card';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function TokenizerNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<BaseNode<TokenizerFormSchemaType>>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={false}
|
||||
showCopy={false}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<LabelCard className="text-text-primary flex justify-between flex-col gap-1">
|
||||
<span className="text-text-secondary">
|
||||
{t('dataflow.searchMethod')}
|
||||
</span>
|
||||
<ul className="space-y-1">
|
||||
{data.form?.search_method.map((x) => (
|
||||
<li key={x}>{t(`dataflow.tokenizerSearchMethodOptions.${x}`)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</LabelCard>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(TokenizerNode);
|
||||
@ -28,6 +28,7 @@ type ToolBarProps = {
|
||||
label: string;
|
||||
id: string;
|
||||
showRun?: boolean;
|
||||
showCopy?: boolean;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export function ToolBar({
|
||||
@ -36,6 +37,7 @@ export function ToolBar({
|
||||
label,
|
||||
id,
|
||||
showRun = true,
|
||||
showCopy = true,
|
||||
}: ToolBarProps) {
|
||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||
const deleteIterationNodeById = useGraphStore(
|
||||
@ -74,10 +76,12 @@ export function ToolBar({
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" data-play />
|
||||
</IconWrapper>
|
||||
)}{' '}
|
||||
<IconWrapper onClick={handleDuplicate}>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{showCopy && (
|
||||
<IconWrapper onClick={handleDuplicate}>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
<IconWrapper onClick={deleteNode}>
|
||||
<Trash2 className="size-3.5" />
|
||||
</IconWrapper>
|
||||
|
||||
@ -6,9 +6,13 @@ import {
|
||||
AgentGlobals,
|
||||
AgentGlobalsSysQueryWithBrace,
|
||||
CodeTemplateStrMap,
|
||||
Operator,
|
||||
ProgrammingLanguage,
|
||||
initialLlmBaseValues,
|
||||
} from '@/constants/agent';
|
||||
export { Operator } from '@/constants/agent';
|
||||
|
||||
export * from './pipeline';
|
||||
|
||||
export enum AgentDialogueMode {
|
||||
Conversational = 'conversational',
|
||||
@ -43,51 +47,6 @@ import {
|
||||
|
||||
export const BeginId = 'begin';
|
||||
|
||||
export enum Operator {
|
||||
Begin = 'Begin',
|
||||
Retrieval = 'Retrieval',
|
||||
Categorize = 'Categorize',
|
||||
Message = 'Message',
|
||||
Relevant = 'Relevant',
|
||||
RewriteQuestion = 'RewriteQuestion',
|
||||
KeywordExtract = 'KeywordExtract',
|
||||
Baidu = 'Baidu',
|
||||
DuckDuckGo = 'DuckDuckGo',
|
||||
Wikipedia = 'Wikipedia',
|
||||
PubMed = 'PubMed',
|
||||
ArXiv = 'ArXiv',
|
||||
Google = 'Google',
|
||||
Bing = 'Bing',
|
||||
GoogleScholar = 'GoogleScholar',
|
||||
DeepL = 'DeepL',
|
||||
GitHub = 'GitHub',
|
||||
BaiduFanyi = 'BaiduFanyi',
|
||||
QWeather = 'QWeather',
|
||||
ExeSQL = 'ExeSQL',
|
||||
Switch = 'Switch',
|
||||
WenCai = 'WenCai',
|
||||
AkShare = 'AkShare',
|
||||
YahooFinance = 'YahooFinance',
|
||||
Jin10 = 'Jin10',
|
||||
TuShare = 'TuShare',
|
||||
Note = 'Note',
|
||||
Crawler = 'Crawler',
|
||||
Invoke = 'Invoke',
|
||||
Email = 'Email',
|
||||
Iteration = 'Iteration',
|
||||
IterationStart = 'IterationItem',
|
||||
Code = 'CodeExec',
|
||||
WaitingDialogue = 'WaitingDialogue',
|
||||
Agent = 'Agent',
|
||||
Tool = 'Tool',
|
||||
TavilySearch = 'TavilySearch',
|
||||
TavilyExtract = 'TavilyExtract',
|
||||
UserFillUp = 'UserFillUp',
|
||||
StringTransform = 'StringTransform',
|
||||
SearXNG = 'SearXNG',
|
||||
Placeholder = 'Placeholder',
|
||||
}
|
||||
|
||||
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
||||
|
||||
export const CommonOperatorList = Object.values(Operator).filter(
|
||||
@ -833,6 +792,11 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.UserFillUp]: [Operator.Begin],
|
||||
[Operator.Tool]: [Operator.Begin],
|
||||
[Operator.Placeholder]: [Operator.Begin],
|
||||
[Operator.Parser]: [Operator.Begin],
|
||||
[Operator.Splitter]: [Operator.Begin],
|
||||
[Operator.HierarchicalMerger]: [Operator.Begin],
|
||||
[Operator.Tokenizer]: [Operator.Begin],
|
||||
[Operator.Extractor]: [Operator.Begin],
|
||||
};
|
||||
|
||||
export const NodeMap = {
|
||||
@ -878,6 +842,12 @@ export const NodeMap = {
|
||||
[Operator.StringTransform]: 'ragNode',
|
||||
[Operator.TavilyExtract]: 'ragNode',
|
||||
[Operator.Placeholder]: 'placeholderNode',
|
||||
[Operator.File]: 'fileNode',
|
||||
[Operator.Parser]: 'parserNode',
|
||||
[Operator.Tokenizer]: 'tokenizerNode',
|
||||
[Operator.Splitter]: 'splitterNode',
|
||||
[Operator.HierarchicalMerger]: 'splitterNode',
|
||||
[Operator.Extractor]: 'contextNode',
|
||||
};
|
||||
|
||||
export enum BeginQueryType {
|
||||
@ -906,6 +876,21 @@ export const NoDebugOperatorsList = [
|
||||
Operator.Iteration,
|
||||
Operator.UserFillUp,
|
||||
Operator.IterationStart,
|
||||
Operator.File,
|
||||
Operator.Parser,
|
||||
Operator.Tokenizer,
|
||||
Operator.Splitter,
|
||||
Operator.HierarchicalMerger,
|
||||
Operator.Extractor,
|
||||
];
|
||||
|
||||
export const NoCopyOperatorsList = [
|
||||
Operator.File,
|
||||
Operator.Parser,
|
||||
Operator.Tokenizer,
|
||||
Operator.Splitter,
|
||||
Operator.HierarchicalMerger,
|
||||
Operator.Extractor,
|
||||
];
|
||||
|
||||
export enum NodeHandleId {
|
||||
272
web/src/pages/agent/constant/pipeline.tsx
Normal file
272
web/src/pages/agent/constant/pipeline.tsx
Normal file
@ -0,0 +1,272 @@
|
||||
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||
import {
|
||||
initialLlmBaseValues,
|
||||
DataflowOperator as Operator,
|
||||
} from '@/constants/agent';
|
||||
|
||||
export enum FileType {
|
||||
PDF = 'pdf',
|
||||
Spreadsheet = 'spreadsheet',
|
||||
Image = 'image',
|
||||
Email = 'email',
|
||||
TextMarkdown = 'text&markdown',
|
||||
Docx = 'word',
|
||||
PowerPoint = 'slides',
|
||||
Video = 'video',
|
||||
Audio = 'audio',
|
||||
}
|
||||
|
||||
export enum PdfOutputFormat {
|
||||
Json = 'json',
|
||||
Markdown = 'markdown',
|
||||
}
|
||||
|
||||
export enum SpreadsheetOutputFormat {
|
||||
Json = 'json',
|
||||
Html = 'html',
|
||||
}
|
||||
|
||||
export enum ImageOutputFormat {
|
||||
Text = 'text',
|
||||
}
|
||||
|
||||
export enum EmailOutputFormat {
|
||||
Json = 'json',
|
||||
Text = 'text',
|
||||
}
|
||||
|
||||
export enum TextMarkdownOutputFormat {
|
||||
Text = 'text',
|
||||
}
|
||||
|
||||
export enum DocxOutputFormat {
|
||||
Markdown = 'markdown',
|
||||
Json = 'json',
|
||||
}
|
||||
|
||||
export enum PptOutputFormat {
|
||||
Json = 'json',
|
||||
}
|
||||
|
||||
export enum VideoOutputFormat {
|
||||
Json = 'json',
|
||||
}
|
||||
|
||||
export enum AudioOutputFormat {
|
||||
Text = 'text',
|
||||
}
|
||||
|
||||
export const OutputFormatMap = {
|
||||
[FileType.PDF]: PdfOutputFormat,
|
||||
[FileType.Spreadsheet]: SpreadsheetOutputFormat,
|
||||
[FileType.Image]: ImageOutputFormat,
|
||||
[FileType.Email]: EmailOutputFormat,
|
||||
[FileType.TextMarkdown]: TextMarkdownOutputFormat,
|
||||
[FileType.Docx]: DocxOutputFormat,
|
||||
[FileType.PowerPoint]: PptOutputFormat,
|
||||
[FileType.Video]: VideoOutputFormat,
|
||||
[FileType.Audio]: AudioOutputFormat,
|
||||
};
|
||||
|
||||
export const InitialOutputFormatMap = {
|
||||
[FileType.PDF]: PdfOutputFormat.Json,
|
||||
[FileType.Spreadsheet]: SpreadsheetOutputFormat.Html,
|
||||
[FileType.Image]: ImageOutputFormat.Text,
|
||||
[FileType.Email]: EmailOutputFormat.Text,
|
||||
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
|
||||
[FileType.Docx]: DocxOutputFormat.Json,
|
||||
[FileType.PowerPoint]: PptOutputFormat.Json,
|
||||
[FileType.Video]: VideoOutputFormat.Json,
|
||||
[FileType.Audio]: AudioOutputFormat.Text,
|
||||
};
|
||||
|
||||
export enum ContextGeneratorFieldName {
|
||||
Summary = 'summary',
|
||||
Keywords = 'keywords',
|
||||
Questions = 'questions',
|
||||
Metadata = 'metadata',
|
||||
}
|
||||
|
||||
export const FileId = 'File'; // BeginId
|
||||
|
||||
export enum TokenizerSearchMethod {
|
||||
Embedding = 'embedding',
|
||||
FullText = 'full_text',
|
||||
}
|
||||
|
||||
export enum ImageParseMethod {
|
||||
OCR = 'ocr',
|
||||
}
|
||||
|
||||
export enum TokenizerFields {
|
||||
Text = 'text',
|
||||
Questions = 'questions',
|
||||
Summary = 'summary',
|
||||
}
|
||||
|
||||
export enum ParserFields {
|
||||
From = 'from',
|
||||
To = 'to',
|
||||
Cc = 'cc',
|
||||
Bcc = 'bcc',
|
||||
Date = 'date',
|
||||
Subject = 'subject',
|
||||
Body = 'body',
|
||||
Attachments = 'attachments',
|
||||
}
|
||||
|
||||
// initialBeginValues
|
||||
export const initialFileValues = {
|
||||
outputs: {
|
||||
name: {
|
||||
type: 'string',
|
||||
value: '',
|
||||
},
|
||||
file: {
|
||||
type: 'Object',
|
||||
value: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const initialTokenizerValues = {
|
||||
search_method: [
|
||||
TokenizerSearchMethod.Embedding,
|
||||
TokenizerSearchMethod.FullText,
|
||||
],
|
||||
filename_embd_weight: 0.1,
|
||||
fields: TokenizerFields.Text,
|
||||
outputs: {},
|
||||
};
|
||||
|
||||
export enum StringTransformMethod {
|
||||
Merge = 'merge',
|
||||
Split = 'split',
|
||||
}
|
||||
|
||||
export enum StringTransformDelimiter {
|
||||
Comma = ',',
|
||||
Semicolon = ';',
|
||||
Period = '.',
|
||||
LineBreak = '\n',
|
||||
Tab = '\t',
|
||||
Space = ' ',
|
||||
}
|
||||
|
||||
export const initialParserValues = {
|
||||
outputs: {
|
||||
markdown: { type: 'string', value: '' },
|
||||
text: { type: 'string', value: '' },
|
||||
html: { type: 'string', value: '' },
|
||||
json: { type: 'Array<object>', value: [] },
|
||||
},
|
||||
setups: [
|
||||
{
|
||||
fileFormat: FileType.PDF,
|
||||
output_format: PdfOutputFormat.Json,
|
||||
parse_method: ParseDocumentType.DeepDOC,
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.Spreadsheet,
|
||||
output_format: SpreadsheetOutputFormat.Html,
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.Image,
|
||||
output_format: ImageOutputFormat.Text,
|
||||
parse_method: ImageParseMethod.OCR,
|
||||
system_prompt: '',
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.Email,
|
||||
fields: Object.values(ParserFields),
|
||||
output_format: EmailOutputFormat.Text,
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.TextMarkdown,
|
||||
output_format: TextMarkdownOutputFormat.Text,
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.Docx,
|
||||
output_format: DocxOutputFormat.Json,
|
||||
},
|
||||
{
|
||||
fileFormat: FileType.PowerPoint,
|
||||
output_format: PptOutputFormat.Json,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const initialSplitterValues = {
|
||||
outputs: {
|
||||
chunks: { type: 'Array<Object>', value: [] },
|
||||
},
|
||||
chunk_token_size: 512,
|
||||
overlapped_percent: 0,
|
||||
delimiters: [{ value: '\n' }],
|
||||
};
|
||||
|
||||
export enum Hierarchy {
|
||||
H1 = '1',
|
||||
H2 = '2',
|
||||
H3 = '3',
|
||||
H4 = '4',
|
||||
H5 = '5',
|
||||
}
|
||||
|
||||
export const initialHierarchicalMergerValues = {
|
||||
outputs: {
|
||||
chunks: { type: 'Array<Object>', value: [] },
|
||||
},
|
||||
hierarchy: Hierarchy.H3,
|
||||
levels: [
|
||||
{ expressions: [{ expression: '^#[^#]' }] },
|
||||
{ expressions: [{ expression: '^##[^#]' }] },
|
||||
{ expressions: [{ expression: '^###[^#]' }] },
|
||||
{ expressions: [{ expression: '^####[^#]' }] },
|
||||
],
|
||||
};
|
||||
|
||||
export const initialExtractorValues = {
|
||||
...initialLlmBaseValues,
|
||||
field_name: ContextGeneratorFieldName.Summary,
|
||||
outputs: {
|
||||
chunks: { type: 'Array<Object>', value: [] },
|
||||
},
|
||||
};
|
||||
|
||||
export const NoDebugOperatorsList = [Operator.Begin];
|
||||
|
||||
export const FileTypeSuffixMap = {
|
||||
[FileType.PDF]: ['pdf'],
|
||||
[FileType.Spreadsheet]: ['xls', 'xlsx', 'csv'],
|
||||
[FileType.Image]: ['jpg', 'jpeg', 'png', 'gif'],
|
||||
[FileType.Email]: ['eml', 'msg'],
|
||||
[FileType.TextMarkdown]: ['md', 'markdown', 'mdx', 'txt'],
|
||||
[FileType.Docx]: ['doc', 'docx'],
|
||||
[FileType.PowerPoint]: ['pptx'],
|
||||
[FileType.Video]: [],
|
||||
[FileType.Audio]: [
|
||||
'da',
|
||||
'wave',
|
||||
'wav',
|
||||
'mp3',
|
||||
'aac',
|
||||
'flac',
|
||||
'ogg',
|
||||
'aiff',
|
||||
'au',
|
||||
'midi',
|
||||
'wma',
|
||||
'realaudio',
|
||||
'vqf',
|
||||
'oggvorbis',
|
||||
'ape',
|
||||
],
|
||||
};
|
||||
|
||||
export const SingleOperators = [
|
||||
Operator.Tokenizer,
|
||||
Operator.Splitter,
|
||||
Operator.HierarchicalMerger,
|
||||
Operator.Parser,
|
||||
];
|
||||
@ -48,3 +48,13 @@ export type HandleContextType = {
|
||||
export const HandleContext = createContext<HandleContextType>(
|
||||
{} as HandleContextType,
|
||||
);
|
||||
|
||||
export type PipelineLogContextType = {
|
||||
messageId: string;
|
||||
setMessageId: (messageId: string) => void;
|
||||
setUploadedFileData: (data: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
export const PipelineLogContext = createContext<PipelineLogContextType>(
|
||||
{} as PipelineLogContextType,
|
||||
);
|
||||
|
||||
@ -13,25 +13,30 @@ import DeepLForm from '../form/deepl-form';
|
||||
import DuckDuckGoForm from '../form/duckduckgo-form';
|
||||
import EmailForm from '../form/email-form';
|
||||
import ExeSQLForm from '../form/exesql-form';
|
||||
import ExtractorForm from '../form/extractor-form';
|
||||
import GithubForm from '../form/github-form';
|
||||
import GoogleForm from '../form/google-form';
|
||||
import GoogleScholarForm from '../form/google-scholar-form';
|
||||
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
||||
import InvokeForm from '../form/invoke-form';
|
||||
import IterationForm from '../form/iteration-form';
|
||||
import IterationStartForm from '../form/iteration-start-from';
|
||||
import Jin10Form from '../form/jin10-form';
|
||||
import KeywordExtractForm from '../form/keyword-extract-form';
|
||||
import MessageForm from '../form/message-form';
|
||||
import ParserForm from '../form/parser-form';
|
||||
import PubMedForm from '../form/pubmed-form';
|
||||
import QWeatherForm from '../form/qweather-form';
|
||||
import RelevantForm from '../form/relevant-form';
|
||||
import RetrievalForm from '../form/retrieval-form/next';
|
||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
||||
import SearXNGForm from '../form/searxng-form';
|
||||
import SplitterForm from '../form/splitter-form';
|
||||
import StringTransformForm from '../form/string-transform-form';
|
||||
import SwitchForm from '../form/switch-form';
|
||||
import TavilyExtractForm from '../form/tavily-extract-form';
|
||||
import TavilyForm from '../form/tavily-form';
|
||||
import TokenizerForm from '../form/tokenizer-form';
|
||||
import ToolForm from '../form/tool-form';
|
||||
import TuShareForm from '../form/tushare-form';
|
||||
import UserFillUpForm from '../form/user-fill-up-form';
|
||||
@ -163,4 +168,26 @@ export const FormConfigMap = {
|
||||
[Operator.TavilyExtract]: {
|
||||
component: TavilyExtractForm,
|
||||
},
|
||||
[Operator.Placeholder]: {
|
||||
component: () => <></>,
|
||||
},
|
||||
// pipeline
|
||||
[Operator.File]: {
|
||||
component: () => <></>,
|
||||
},
|
||||
[Operator.Parser]: {
|
||||
component: ParserForm,
|
||||
},
|
||||
[Operator.Tokenizer]: {
|
||||
component: TokenizerForm,
|
||||
},
|
||||
[Operator.Splitter]: {
|
||||
component: SplitterForm,
|
||||
},
|
||||
[Operator.HierarchicalMerger]: {
|
||||
component: HierarchicalMergerForm,
|
||||
},
|
||||
[Operator.Extractor]: {
|
||||
component: ExtractorForm,
|
||||
},
|
||||
};
|
||||
|
||||
107
web/src/pages/agent/form/extractor-form/index.tsx
Normal file
107
web/src/pages/agent/form/extractor-form/index.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { memo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
ContextGeneratorFieldName,
|
||||
initialExtractorValues,
|
||||
} from '../../constant/pipeline';
|
||||
import { useBuildNodeOutputOptions } from '../../hooks/use-build-options';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
import { useSwitchPrompt } from './use-switch-prompt';
|
||||
|
||||
export const FormSchema = z.object({
|
||||
field_name: z.string(),
|
||||
sys_prompt: z.string(),
|
||||
prompts: z.string().optional(),
|
||||
...LlmSettingSchema,
|
||||
});
|
||||
|
||||
export type ExtractorFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
const outputList = buildOutputList(initialExtractorValues.outputs);
|
||||
|
||||
const ExtractorForm = ({ node }: INextOperatorForm) => {
|
||||
const defaultValues = useFormValues(initialExtractorValues, node);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<ExtractorFormSchemaType>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
// mode: 'onChange',
|
||||
});
|
||||
|
||||
const promptOptions = useBuildNodeOutputOptions(node?.id);
|
||||
|
||||
const options = buildOptions(ContextGeneratorFieldName, t, 'dataflow');
|
||||
|
||||
const {
|
||||
handleFieldNameChange,
|
||||
confirmSwitch,
|
||||
hideModal,
|
||||
visible,
|
||||
cancelSwitch,
|
||||
} = useSwitchPrompt(form);
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<LargeModelFormField></LargeModelFormField>
|
||||
<RAGFlowFormItem label={t('dataflow.fieldName')} name="field_name">
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
onChange={(value) => {
|
||||
field.onChange(value);
|
||||
handleFieldNameChange(value);
|
||||
}}
|
||||
value={field.value}
|
||||
placeholder={t('dataFlowPlaceholder')}
|
||||
options={options}
|
||||
></SelectWithSearch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem label={t('flow.systemPrompt')} name="sys_prompt">
|
||||
<PromptEditor
|
||||
placeholder={t('flow.messagePlaceholder')}
|
||||
showToolbar={true}
|
||||
baseOptions={promptOptions}
|
||||
></PromptEditor>
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
|
||||
<PromptEditor
|
||||
showToolbar={true}
|
||||
baseOptions={promptOptions}
|
||||
></PromptEditor>
|
||||
</RAGFlowFormItem>
|
||||
<Output list={outputList}></Output>
|
||||
</FormWrapper>
|
||||
{visible && (
|
||||
<ConfirmDeleteDialog
|
||||
title={t('dataflow.switchPromptMessage')}
|
||||
open
|
||||
onOpenChange={hideModal}
|
||||
onOk={confirmSwitch}
|
||||
onCancel={cancelSwitch}
|
||||
></ConfirmDeleteDialog>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ExtractorForm);
|
||||
69
web/src/pages/agent/form/extractor-form/use-switch-prompt.ts
Normal file
69
web/src/pages/agent/form/extractor-form/use-switch-prompt.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FormSchema = z.object({
|
||||
field_name: z.string(),
|
||||
sys_prompt: z.string(),
|
||||
prompts: z.string().optional(),
|
||||
...LlmSettingSchema,
|
||||
});
|
||||
|
||||
export type ExtractorFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
export function useSwitchPrompt(form: UseFormReturn<ExtractorFormSchemaType>) {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
const { t } = useTranslation();
|
||||
const previousFieldNames = useRef<string[]>([form.getValues('field_name')]);
|
||||
|
||||
const setPromptValue = useCallback(
|
||||
(field: keyof ExtractorFormSchemaType, key: string, value: string) => {
|
||||
form.setValue(field, t(`dataflow.prompts.${key}.${value}`), {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
},
|
||||
[form, t],
|
||||
);
|
||||
|
||||
const handleFieldNameChange = useCallback(
|
||||
(value: string) => {
|
||||
if (value) {
|
||||
const names = previousFieldNames.current;
|
||||
if (names.length > 1) {
|
||||
names.shift();
|
||||
}
|
||||
names.push(value);
|
||||
showModal();
|
||||
}
|
||||
},
|
||||
[showModal],
|
||||
);
|
||||
|
||||
const confirmSwitch = useCallback(() => {
|
||||
const value = form.getValues('field_name');
|
||||
setPromptValue('sys_prompt', 'system', value);
|
||||
setPromptValue('prompts', 'user', value);
|
||||
}, [form, setPromptValue]);
|
||||
|
||||
const cancelSwitch = useCallback(() => {
|
||||
const previousValue = previousFieldNames.current.at(-2);
|
||||
if (previousValue) {
|
||||
form.setValue('field_name', previousValue, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
return {
|
||||
handleFieldNameChange,
|
||||
confirmSwitch,
|
||||
hideModal,
|
||||
visible,
|
||||
cancelSwitch,
|
||||
};
|
||||
}
|
||||
191
web/src/pages/agent/form/hierarchical-merger-form/index.tsx
Normal file
191
web/src/pages/agent/form/hierarchical-merger-form/index.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Form, FormLabel } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Hierarchy,
|
||||
initialHierarchicalMergerValues,
|
||||
} from '../../constant/pipeline';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
|
||||
const outputList = buildOutputList(initialHierarchicalMergerValues.outputs);
|
||||
|
||||
const HierarchyOptions = [
|
||||
{ label: 'H1', value: Hierarchy.H1 },
|
||||
{ label: 'H2', value: Hierarchy.H2 },
|
||||
{ label: 'H3', value: Hierarchy.H3 },
|
||||
{ label: 'H4', value: Hierarchy.H4 },
|
||||
{ label: 'H5', value: Hierarchy.H5 },
|
||||
];
|
||||
|
||||
export const FormSchema = z.object({
|
||||
hierarchy: z.string(),
|
||||
levels: z.array(
|
||||
z.object({
|
||||
expressions: z.array(
|
||||
z.object({
|
||||
expression: z.string().refine(
|
||||
(val) => {
|
||||
try {
|
||||
// Try converting the string to a RegExp
|
||||
new RegExp(val);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Must be a valid regular expression string',
|
||||
},
|
||||
),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type HierarchicalMergerFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
type RegularExpressionsProps = {
|
||||
index: number;
|
||||
parentName: string;
|
||||
removeParent: (index: number) => void;
|
||||
isLatest: boolean;
|
||||
};
|
||||
|
||||
export function RegularExpressions({
|
||||
index,
|
||||
parentName,
|
||||
isLatest,
|
||||
removeParent,
|
||||
}: RegularExpressionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
|
||||
const name = `${parentName}.${index}.expressions`;
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex-row justify-between items-center">
|
||||
<span>H{index + 1}</span>
|
||||
{isLatest && (
|
||||
<Button
|
||||
type="button"
|
||||
variant={'ghost'}
|
||||
onClick={() => removeParent(index)}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FormLabel required className="mb-2 text-text-secondary">
|
||||
{t('dataflow.regularExpressions')}
|
||||
</FormLabel>
|
||||
<section className="space-y-4">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<div className="space-y-2 flex-1">
|
||||
<RAGFlowFormItem
|
||||
name={`${name}.${index}.expression`}
|
||||
label={'expression'}
|
||||
labelClassName="!hidden"
|
||||
>
|
||||
<Input className="!m-0"></Input>
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
{index === 0 ? (
|
||||
<Button
|
||||
onClick={() => append({ expression: '' })}
|
||||
variant={'ghost'}
|
||||
>
|
||||
<Plus></Plus>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant={'ghost'}
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
|
||||
|
||||
const form = useForm<HierarchicalMergerFormSchemaType>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const name = 'levels';
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<RAGFlowFormItem name={'hierarchy'} label={t('dataflow.hierarchy')}>
|
||||
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
|
||||
</RAGFlowFormItem>
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex items-center">
|
||||
<div className="flex-1">
|
||||
<RegularExpressions
|
||||
parentName={name}
|
||||
index={index}
|
||||
removeParent={remove}
|
||||
isLatest={index === fields.length - 1}
|
||||
></RegularExpressions>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{fields.length < 5 && (
|
||||
<BlockButton
|
||||
onClick={() => append({ expressions: [{ expression: '' }] })}
|
||||
>
|
||||
{t('common.add')}
|
||||
</BlockButton>
|
||||
)}
|
||||
</FormWrapper>
|
||||
<div className="p-5">
|
||||
<Output list={outputList}></Output>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HierarchicalMergerForm);
|
||||
106
web/src/pages/agent/form/parser-form/common-form-fields.tsx
Normal file
106
web/src/pages/agent/form/parser-form/common-form-fields.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { crossLanguageOptions } from '@/components/cross-language-form-field';
|
||||
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
|
||||
import {
|
||||
LLMFormField,
|
||||
LLMFormFieldProps,
|
||||
} from '@/components/llm-setting-items/llm-form-field';
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { upperCase, upperFirst } from 'lodash';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FileType,
|
||||
OutputFormatMap,
|
||||
SpreadsheetOutputFormat,
|
||||
} from '../../constant/pipeline';
|
||||
import { CommonProps } from './interface';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
const UppercaseFields = [
|
||||
SpreadsheetOutputFormat.Html,
|
||||
SpreadsheetOutputFormat.Json,
|
||||
];
|
||||
|
||||
function buildOutputOptionsFormatMap() {
|
||||
return Object.entries(OutputFormatMap).reduce<
|
||||
Record<string, SelectWithSearchFlagOptionType[]>
|
||||
>((pre, [key, value]) => {
|
||||
pre[key] = Object.values(value).map((v) => ({
|
||||
label: UppercaseFields.some((x) => x === v)
|
||||
? upperCase(v)
|
||||
: upperFirst(v),
|
||||
value: v,
|
||||
}));
|
||||
return pre;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export type OutputFormatFormFieldProps = CommonProps & {
|
||||
fileType: FileType;
|
||||
};
|
||||
|
||||
export function OutputFormatFormField({
|
||||
prefix,
|
||||
fileType,
|
||||
}: OutputFormatFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={buildFieldNameWithPrefix(`output_format`, prefix)}
|
||||
label={t('dataflow.outputFormat')}
|
||||
>
|
||||
<SelectWithSearch
|
||||
options={buildOutputOptionsFormatMap()[fileType]}
|
||||
></SelectWithSearch>
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ParserMethodFormField({
|
||||
prefix,
|
||||
optionsWithoutLLM,
|
||||
}: CommonProps & { optionsWithoutLLM?: { value: string; label: string }[] }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<LayoutRecognizeFormField
|
||||
name={buildFieldNameWithPrefix(`parse_method`, prefix)}
|
||||
horizontal={false}
|
||||
optionsWithoutLLM={optionsWithoutLLM}
|
||||
label={t('dataflow.parserMethod')}
|
||||
></LayoutRecognizeFormField>
|
||||
);
|
||||
}
|
||||
|
||||
export function LargeModelFormField({
|
||||
prefix,
|
||||
options,
|
||||
}: CommonProps & Pick<LLMFormFieldProps, 'options'>) {
|
||||
return (
|
||||
<LLMFormField
|
||||
name={buildFieldNameWithPrefix('llm_id', prefix)}
|
||||
options={options}
|
||||
></LLMFormField>
|
||||
);
|
||||
}
|
||||
|
||||
export function LanguageFormField({ prefix }: CommonProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={buildFieldNameWithPrefix(`lang`, prefix)}
|
||||
label={t('dataflow.lang')}
|
||||
>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
options={crossLanguageOptions}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
></SelectWithSearch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
30
web/src/pages/agent/form/parser-form/email-form-fields.tsx
Normal file
30
web/src/pages/agent/form/parser-form/email-form-fields.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ParserFields } from '../../constant/pipeline';
|
||||
import { CommonProps } from './interface';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
const options = buildOptions(ParserFields);
|
||||
|
||||
export function EmailFormFields({ prefix }: CommonProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<RAGFlowFormItem
|
||||
name={buildFieldNameWithPrefix(`fields`, prefix)}
|
||||
label={t('dataflow.fields')}
|
||||
>
|
||||
{(field) => (
|
||||
<MultiSelect
|
||||
options={options}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
variant="inverted"
|
||||
></MultiSelect>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
60
web/src/pages/agent/form/parser-form/image-form-fields.tsx
Normal file
60
web/src/pages/agent/form/parser-form/image-form-fields.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ImageParseMethod } from '../../constant/pipeline';
|
||||
import { LanguageFormField, ParserMethodFormField } from './common-form-fields';
|
||||
import { CommonProps } from './interface';
|
||||
import { useSetInitialLanguage } from './use-set-initial-language';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function ImageFormFields({ prefix }: CommonProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const options = buildOptions(
|
||||
ImageParseMethod,
|
||||
t,
|
||||
'dataflow.imageParseMethodOptions',
|
||||
);
|
||||
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
|
||||
|
||||
const parseMethod = useWatch({
|
||||
name: parseMethodName,
|
||||
});
|
||||
|
||||
const languageShown = useMemo(() => {
|
||||
return !isEmpty(parseMethod) && parseMethod !== ImageParseMethod.OCR;
|
||||
}, [parseMethod]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmpty(form.getValues(parseMethodName))) {
|
||||
form.setValue(parseMethodName, ImageParseMethod.OCR, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
}
|
||||
}, [form, parseMethodName]);
|
||||
|
||||
useSetInitialLanguage({ prefix, languageShown });
|
||||
|
||||
return (
|
||||
<>
|
||||
<ParserMethodFormField
|
||||
prefix={prefix}
|
||||
optionsWithoutLLM={options}
|
||||
></ParserMethodFormField>
|
||||
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
|
||||
{languageShown && (
|
||||
<RAGFlowFormItem
|
||||
name={buildFieldNameWithPrefix('system_prompt', prefix)}
|
||||
label={t('dataflow.systemPrompt')}
|
||||
>
|
||||
<Textarea placeholder={t('dataflow.systemPromptPlaceholder')} />
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
226
web/src/pages/agent/form/parser-form/index.tsx
Normal file
226
web/src/pages/agent/form/parser-form/index.tsx
Normal file
@ -0,0 +1,226 @@
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useHover } from 'ahooks';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
UseFieldArrayRemove,
|
||||
useFieldArray,
|
||||
useForm,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
FileType,
|
||||
InitialOutputFormatMap,
|
||||
initialParserValues,
|
||||
} from '../../constant/pipeline';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { Output } from '../components/output';
|
||||
import { OutputFormatFormField } from './common-form-fields';
|
||||
import { EmailFormFields } from './email-form-fields';
|
||||
import { ImageFormFields } from './image-form-fields';
|
||||
import { PdfFormFields } from './pdf-form-fields';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
import { VideoFormFields } from './video-form-fields';
|
||||
|
||||
const outputList = buildOutputList(initialParserValues.outputs);
|
||||
|
||||
const FileFormatWidgetMap = {
|
||||
[FileType.PDF]: PdfFormFields,
|
||||
[FileType.Video]: VideoFormFields,
|
||||
[FileType.Audio]: VideoFormFields,
|
||||
[FileType.Email]: EmailFormFields,
|
||||
[FileType.Image]: ImageFormFields,
|
||||
};
|
||||
|
||||
type ParserItemProps = {
|
||||
name: string;
|
||||
index: number;
|
||||
fieldLength: number;
|
||||
remove: UseFieldArrayRemove;
|
||||
fileFormatOptions: SelectWithSearchFlagOptionType[];
|
||||
};
|
||||
|
||||
export const FormSchema = z.object({
|
||||
setups: z.array(
|
||||
z.object({
|
||||
fileFormat: z.string().nullish(),
|
||||
output_format: z.string().optional(),
|
||||
parse_method: z.string().optional(),
|
||||
lang: z.string().optional(),
|
||||
fields: z.array(z.string()).optional(),
|
||||
llm_id: z.string().optional(),
|
||||
system_prompt: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type ParserFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
function ParserItem({
|
||||
name,
|
||||
index,
|
||||
fieldLength,
|
||||
remove,
|
||||
fileFormatOptions,
|
||||
}: ParserItemProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext<ParserFormSchemaType>();
|
||||
const ref = useRef(null);
|
||||
const isHovering = useHover(ref);
|
||||
|
||||
const prefix = `${name}.${index}`;
|
||||
const fileFormat = form.getValues(`setups.${index}.fileFormat`);
|
||||
|
||||
const values = form.getValues();
|
||||
const parserList = values.setups.slice(); // Adding, deleting, or modifying the parser array will not change the reference.
|
||||
|
||||
const filteredFileFormatOptions = useMemo(() => {
|
||||
const otherFileFormatList = parserList
|
||||
.filter((_, idx) => idx !== index)
|
||||
.map((x) => x.fileFormat);
|
||||
|
||||
return fileFormatOptions.filter((x) => {
|
||||
return !otherFileFormatList.includes(x.value);
|
||||
});
|
||||
}, [fileFormatOptions, index, parserList]);
|
||||
|
||||
const Widget =
|
||||
typeof fileFormat === 'string' && fileFormat in FileFormatWidgetMap
|
||||
? FileFormatWidgetMap[fileFormat as keyof typeof FileFormatWidgetMap]
|
||||
: () => <></>;
|
||||
|
||||
const handleFileTypeChange = useCallback(
|
||||
(value: FileType) => {
|
||||
form.setValue(
|
||||
`setups.${index}.output_format`,
|
||||
InitialOutputFormatMap[value],
|
||||
{ shouldDirty: true, shouldValidate: true, shouldTouch: true },
|
||||
);
|
||||
},
|
||||
[form, index],
|
||||
);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn('space-y-5 py-2.5 rounded-md', {
|
||||
'bg-state-error-5': isHovering,
|
||||
})}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-text-primary text-sm font-medium">
|
||||
Parser {index + 1}
|
||||
</span>
|
||||
{index > 0 && (
|
||||
<Button variant={'ghost'} onClick={() => remove(index)} ref={ref}>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<RAGFlowFormItem
|
||||
name={buildFieldNameWithPrefix(`fileFormat`, prefix)}
|
||||
label={t('dataflow.fileFormats')}
|
||||
>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
handleFileTypeChange(val as FileType);
|
||||
}}
|
||||
options={filteredFileFormatOptions}
|
||||
></SelectWithSearch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<Widget prefix={prefix} fileType={fileFormat as FileType}></Widget>
|
||||
<div className="hidden">
|
||||
<OutputFormatFormField
|
||||
prefix={prefix}
|
||||
fileType={fileFormat as FileType}
|
||||
/>
|
||||
</div>
|
||||
{index < fieldLength - 1 && <Separator />}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const ParserForm = ({ node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultValues = useFormValues(initialParserValues, node);
|
||||
|
||||
const FileFormatOptions = buildOptions(
|
||||
FileType,
|
||||
t,
|
||||
'dataflow.fileFormatOptions',
|
||||
).filter(
|
||||
(x) => x.value !== FileType.Video, // Temporarily hide the video option
|
||||
);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
shouldUnregister: true,
|
||||
});
|
||||
|
||||
const name = 'setups';
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const add = useCallback(() => {
|
||||
append({
|
||||
fileFormat: null,
|
||||
output_format: '',
|
||||
parse_method: '',
|
||||
lang: '',
|
||||
fields: [],
|
||||
llm_id: '',
|
||||
});
|
||||
}, [append]);
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form className="px-5">
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<ParserItem
|
||||
key={field.id}
|
||||
name={name}
|
||||
index={index}
|
||||
fieldLength={fields.length}
|
||||
remove={remove}
|
||||
fileFormatOptions={FileFormatOptions}
|
||||
></ParserItem>
|
||||
);
|
||||
})}
|
||||
{fields.length < FileFormatOptions.length && (
|
||||
<BlockButton onClick={add} type="button" className="mt-2.5">
|
||||
{t('dataflow.addParser')}
|
||||
</BlockButton>
|
||||
)}
|
||||
</form>
|
||||
<div className="p-5">
|
||||
<Output list={outputList}></Output>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParserForm);
|
||||
3
web/src/pages/agent/form/parser-form/interface.ts
Normal file
3
web/src/pages/agent/form/parser-form/interface.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type CommonProps = {
|
||||
prefix: string;
|
||||
};
|
||||
44
web/src/pages/agent/form/parser-form/pdf-form-fields.tsx
Normal file
44
web/src/pages/agent/form/parser-form/pdf-form-fields.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { LanguageFormField, ParserMethodFormField } from './common-form-fields';
|
||||
import { CommonProps } from './interface';
|
||||
import { useSetInitialLanguage } from './use-set-initial-language';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function PdfFormFields({ prefix }: CommonProps) {
|
||||
const form = useFormContext();
|
||||
|
||||
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
|
||||
|
||||
const parseMethod = useWatch({
|
||||
name: parseMethodName,
|
||||
});
|
||||
|
||||
const languageShown = useMemo(() => {
|
||||
return (
|
||||
!isEmpty(parseMethod) &&
|
||||
parseMethod !== ParseDocumentType.DeepDOC &&
|
||||
parseMethod !== ParseDocumentType.PlainText
|
||||
);
|
||||
}, [parseMethod]);
|
||||
|
||||
useSetInitialLanguage({ prefix, languageShown });
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmpty(form.getValues(parseMethodName))) {
|
||||
form.setValue(parseMethodName, ParseDocumentType.DeepDOC, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
}
|
||||
}, [form, parseMethodName]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
|
||||
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { crossLanguageOptions } from '@/components/cross-language-form-field';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function useSetInitialLanguage({
|
||||
prefix,
|
||||
languageShown,
|
||||
}: {
|
||||
prefix: string;
|
||||
languageShown: boolean;
|
||||
}) {
|
||||
const form = useFormContext();
|
||||
const lang = form.getValues(buildFieldNameWithPrefix('lang', prefix));
|
||||
|
||||
useEffect(() => {
|
||||
if (languageShown && isEmpty(lang)) {
|
||||
form.setValue(
|
||||
buildFieldNameWithPrefix('lang', prefix),
|
||||
crossLanguageOptions[0].value,
|
||||
{
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, [form, lang, languageShown, prefix]);
|
||||
}
|
||||
3
web/src/pages/agent/form/parser-form/utils.ts
Normal file
3
web/src/pages/agent/form/parser-form/utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function buildFieldNameWithPrefix(name: string, prefix: string) {
|
||||
return `${prefix}.${name}`;
|
||||
}
|
||||
22
web/src/pages/agent/form/parser-form/video-form-fields.tsx
Normal file
22
web/src/pages/agent/form/parser-form/video-form-fields.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
|
||||
import {
|
||||
LargeModelFormField,
|
||||
OutputFormatFormFieldProps,
|
||||
} from './common-form-fields';
|
||||
|
||||
export function VideoFormFields({ prefix }: OutputFormatFormFieldProps) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Speech2text,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Multimodal Model */}
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
</>
|
||||
);
|
||||
}
|
||||
101
web/src/pages/agent/form/splitter-form/index.tsx
Normal file
101
web/src/pages/agent/form/splitter-form/index.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { DelimiterInput } from '@/components/delimiter-form-field';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { initialSplitterValues } from '../../constant/pipeline';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
|
||||
const outputList = buildOutputList(initialSplitterValues.outputs);
|
||||
|
||||
export const FormSchema = z.object({
|
||||
chunk_token_size: z.number(),
|
||||
delimiters: z.array(
|
||||
z.object({
|
||||
value: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
overlapped_percent: z.number(), // 0.0 - 0.3 , 0% - 30%
|
||||
});
|
||||
|
||||
export type SplitterFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
const SplitterForm = ({ node }: INextOperatorForm) => {
|
||||
const defaultValues = useFormValues(initialSplitterValues, node);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<SplitterFormSchemaType>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
const name = 'delimiters';
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<SliderInputFormField
|
||||
name="chunk_token_size"
|
||||
max={2048}
|
||||
label={t('knowledgeConfiguration.chunkTokenNumber')}
|
||||
></SliderInputFormField>
|
||||
<SliderInputFormField
|
||||
name="overlapped_percent"
|
||||
max={30}
|
||||
min={0}
|
||||
label={t('dataflow.overlappedPercent')}
|
||||
></SliderInputFormField>
|
||||
<section>
|
||||
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
|
||||
<div className="space-y-4">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<div className="space-y-2 flex-1">
|
||||
<RAGFlowFormItem
|
||||
name={`${name}.${index}.value`}
|
||||
label="delimiter"
|
||||
labelClassName="!hidden"
|
||||
>
|
||||
<DelimiterInput className="!m-0"></DelimiterInput>
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant={'ghost'}
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<BlockButton onClick={() => append({ value: '\n' })}>
|
||||
{t('common.add')}
|
||||
</BlockButton>
|
||||
</FormWrapper>
|
||||
<div className="p-5">
|
||||
<Output list={outputList}></Output>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SplitterForm);
|
||||
91
web/src/pages/agent/form/tokenizer-form/index.tsx
Normal file
91
web/src/pages/agent/form/tokenizer-form/index.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { memo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
initialTokenizerValues,
|
||||
TokenizerFields,
|
||||
TokenizerSearchMethod,
|
||||
} from '../../constant';
|
||||
import { useFormValues } from '../../hooks/use-form-values';
|
||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
|
||||
const outputList = buildOutputList(initialTokenizerValues.outputs);
|
||||
|
||||
export const FormSchema = z.object({
|
||||
search_method: z.array(z.string()).min(1),
|
||||
filename_embd_weight: z.number(),
|
||||
fields: z.string(),
|
||||
});
|
||||
|
||||
export type TokenizerFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
const TokenizerForm = ({ node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const defaultValues = useFormValues(initialTokenizerValues, node);
|
||||
|
||||
const SearchMethodOptions = buildOptions(
|
||||
TokenizerSearchMethod,
|
||||
t,
|
||||
`dataflow.tokenizerSearchMethodOptions`,
|
||||
);
|
||||
const FieldsOptions = buildOptions(
|
||||
TokenizerFields,
|
||||
t,
|
||||
'dataflow.tokenizerFieldsOptions',
|
||||
);
|
||||
|
||||
const form = useForm<TokenizerFormSchemaType>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<RAGFlowFormItem
|
||||
name="search_method"
|
||||
label={t('dataflow.searchMethod')}
|
||||
tooltip={t('dataflow.searchMethodTip')}
|
||||
>
|
||||
{(field) => (
|
||||
<MultiSelect
|
||||
options={SearchMethodOptions}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
variant="inverted"
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<SliderInputFormField
|
||||
name="filename_embd_weight"
|
||||
label={t('dataflow.filenameEmbeddingWeight')}
|
||||
max={0.5}
|
||||
step={0.01}
|
||||
></SliderInputFormField>
|
||||
<RAGFlowFormItem name="fields" label={t('dataflow.fields')}>
|
||||
{(field) => <SelectWithSearch options={FieldsOptions} {...field} />}
|
||||
</RAGFlowFormItem>
|
||||
</FormWrapper>
|
||||
<div className="p-5">
|
||||
<Output list={outputList}></Output>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TokenizerForm);
|
||||
@ -23,9 +23,11 @@ import {
|
||||
initialDuckValues,
|
||||
initialEmailValues,
|
||||
initialExeSqlValues,
|
||||
initialExtractorValues,
|
||||
initialGithubValues,
|
||||
initialGoogleScholarValues,
|
||||
initialGoogleValues,
|
||||
initialHierarchicalMergerValues,
|
||||
initialInvokeValues,
|
||||
initialIterationStartValues,
|
||||
initialIterationValues,
|
||||
@ -33,16 +35,19 @@ import {
|
||||
initialKeywordExtractValues,
|
||||
initialMessageValues,
|
||||
initialNoteValues,
|
||||
initialParserValues,
|
||||
initialPubMedValues,
|
||||
initialQWeatherValues,
|
||||
initialRelevantValues,
|
||||
initialRetrievalValues,
|
||||
initialRewriteQuestionValues,
|
||||
initialSearXNGValues,
|
||||
initialSplitterValues,
|
||||
initialStringTransformValues,
|
||||
initialSwitchValues,
|
||||
initialTavilyExtractValues,
|
||||
initialTavilyValues,
|
||||
initialTokenizerValues,
|
||||
initialTuShareValues,
|
||||
initialUserFillUpValues,
|
||||
initialWaitingDialogueValues,
|
||||
@ -115,6 +120,17 @@ export const useInitializeOperatorParams = () => {
|
||||
[Operator.StringTransform]: initialStringTransformValues,
|
||||
[Operator.TavilyExtract]: initialTavilyExtractValues,
|
||||
[Operator.Placeholder]: {},
|
||||
[Operator.File]: {},
|
||||
[Operator.Parser]: initialParserValues,
|
||||
[Operator.Tokenizer]: initialTokenizerValues,
|
||||
[Operator.Splitter]: initialSplitterValues,
|
||||
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
||||
[Operator.Extractor]: {
|
||||
...initialExtractorValues,
|
||||
llm_id: llmId,
|
||||
sys_prompt: t('dataflow.prompts.system.summary'),
|
||||
prompts: t('dataflow.prompts.user.summary'),
|
||||
},
|
||||
};
|
||||
}, [llmId]);
|
||||
|
||||
|
||||
19
web/src/pages/agent/hooks/use-build-options.tsx
Normal file
19
web/src/pages/agent/hooks/use-build-options.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { buildNodeOutputOptions } from '@/utils/canvas-util';
|
||||
import { useMemo } from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
|
||||
return useMemo(() => {
|
||||
return buildNodeOutputOptions({
|
||||
nodes,
|
||||
edges,
|
||||
nodeId,
|
||||
Icon: ({ name }) => <OperatorIcon name={name as Operator}></OperatorIcon>,
|
||||
});
|
||||
}, [edges, nodeId, nodes]);
|
||||
}
|
||||
21
web/src/pages/agent/hooks/use-cancel-dataflow.ts
Normal file
21
web/src/pages/agent/hooks/use-cancel-dataflow.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useCancelDataflow } from '@/hooks/use-agent-request';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useCancelCurrentDataflow({
|
||||
messageId,
|
||||
stopFetchTrace,
|
||||
}: {
|
||||
messageId: string;
|
||||
stopFetchTrace(): void;
|
||||
}) {
|
||||
const { cancelDataflow } = useCancelDataflow();
|
||||
|
||||
const handleCancel = useCallback(async () => {
|
||||
const code = await cancelDataflow(messageId);
|
||||
if (code === 0) {
|
||||
stopFetchTrace();
|
||||
}
|
||||
}, [cancelDataflow, messageId, stopFetchTrace]);
|
||||
|
||||
return { handleCancel };
|
||||
}
|
||||
@ -201,6 +201,7 @@ export const useConnectionDrag = (
|
||||
}, [removePlaceholderNode, hideModal, clearActiveDropdown]);
|
||||
|
||||
return {
|
||||
nodeId: connectionStartRef.current?.nodeId,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
handleConnect,
|
||||
|
||||
38
web/src/pages/agent/hooks/use-download-output.ts
Normal file
38
web/src/pages/agent/hooks/use-download-output.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { ITraceData } from '@/interfaces/database/agent';
|
||||
import { downloadJsonFile } from '@/utils/file-util';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function findEndOutput(list?: ITraceData[]) {
|
||||
if (Array.isArray(list)) {
|
||||
const trace = list.find((x) => x.component_id === 'END')?.trace;
|
||||
|
||||
const str = get(trace, '0.message');
|
||||
|
||||
try {
|
||||
if (!isEmpty(str)) {
|
||||
const json = JSON.parse(str);
|
||||
return json;
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
|
||||
export function isEndOutputEmpty(list?: ITraceData[]) {
|
||||
return isEmpty(findEndOutput(list));
|
||||
}
|
||||
export function useDownloadOutput(data?: ITraceData[]) {
|
||||
const { data: agent } = useFetchAgent();
|
||||
|
||||
const handleDownloadJson = useCallback(() => {
|
||||
const output = findEndOutput(data);
|
||||
if (!isEndOutputEmpty(data)) {
|
||||
downloadJsonFile(output, `${agent.title}.json`);
|
||||
}
|
||||
}, [agent.title, data]);
|
||||
|
||||
return {
|
||||
handleDownloadJson,
|
||||
};
|
||||
}
|
||||
56
web/src/pages/agent/hooks/use-fetch-pipeline-log.ts
Normal file
56
web/src/pages/agent/hooks/use-fetch-pipeline-log.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
export function useFetchPipelineLog(logSheetVisible: boolean) {
|
||||
const {
|
||||
setMessageId,
|
||||
data,
|
||||
loading,
|
||||
messageId,
|
||||
setISStopFetchTrace,
|
||||
isStopFetchTrace,
|
||||
} = useFetchMessageTrace();
|
||||
|
||||
const isCompleted = useMemo(() => {
|
||||
if (Array.isArray(data)) {
|
||||
const latest = data?.at(-1);
|
||||
return (
|
||||
latest?.component_id === 'END' && !isEmpty(latest?.trace[0].message)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}, [data]);
|
||||
|
||||
const isLogEmpty = !data || !data.length;
|
||||
|
||||
const stopFetchTrace = useCallback(() => {
|
||||
setISStopFetchTrace(true);
|
||||
}, [setISStopFetchTrace]);
|
||||
|
||||
// cancel request
|
||||
useEffect(() => {
|
||||
if (isCompleted) {
|
||||
stopFetchTrace();
|
||||
}
|
||||
}, [isCompleted, stopFetchTrace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logSheetVisible) {
|
||||
setISStopFetchTrace(false);
|
||||
}
|
||||
}, [logSheetVisible, setISStopFetchTrace]);
|
||||
|
||||
return {
|
||||
logs: data,
|
||||
isLogEmpty,
|
||||
isCompleted,
|
||||
loading,
|
||||
isParsing: !isLogEmpty && !isCompleted && !isStopFetchTrace,
|
||||
messageId,
|
||||
setMessageId,
|
||||
stopFetchTrace,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseFetchLogReturnType = ReturnType<typeof useFetchPipelineLog>;
|
||||
10
web/src/pages/agent/hooks/use-is-pipeline.ts
Normal file
10
web/src/pages/agent/hooks/use-is-pipeline.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { AgentCategory, AgentQuery } from '@/constants/agent';
|
||||
import { useSearchParams } from 'umi';
|
||||
|
||||
export function useIsPipeline() {
|
||||
const [queryParameters] = useSearchParams();
|
||||
|
||||
return (
|
||||
queryParameters.get(AgentQuery.Category) === AgentCategory.DataflowCanvas
|
||||
);
|
||||
}
|
||||
@ -17,6 +17,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import message from '@/components/ui/message';
|
||||
import { SharedFrom } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
@ -31,20 +32,27 @@ import {
|
||||
Settings,
|
||||
Upload,
|
||||
} from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef, useCallback } from 'react';
|
||||
import { ComponentPropsWithoutRef, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import AgentCanvas from './canvas';
|
||||
import { DropdownProvider } from './canvas/context';
|
||||
import { Operator } from './constant';
|
||||
import { PipelineLogContext } from './context';
|
||||
import { useCancelCurrentDataflow } from './hooks/use-cancel-dataflow';
|
||||
import { useHandleExportJsonFile } from './hooks/use-export-json';
|
||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||
import { useFetchPipelineLog } from './hooks/use-fetch-pipeline-log';
|
||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
||||
import { useIsPipeline } from './hooks/use-is-pipeline';
|
||||
import {
|
||||
useSaveGraph,
|
||||
useSaveGraphBeforeOpeningDebugDrawer,
|
||||
useWatchAgentChange,
|
||||
} from './hooks/use-save-graph';
|
||||
import { PipelineLogSheet } from './pipeline-log-sheet';
|
||||
import { SettingDialog } from './setting-dialog';
|
||||
import useGraphStore from './store';
|
||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||
import { VersionDialog } from './version-dialog';
|
||||
|
||||
@ -61,6 +69,7 @@ function AgentDropdownMenuItem({
|
||||
|
||||
export default function Agent() {
|
||||
const { id } = useParams();
|
||||
const isPipeline = useIsPipeline();
|
||||
const { navigateToAgents } = useNavigatePage();
|
||||
const {
|
||||
visible: chatDrawerVisible,
|
||||
@ -99,6 +108,63 @@ export default function Agent() {
|
||||
const { navigateToAgentLogs } = useNavigatePage();
|
||||
const time = useWatchAgentChange(chatDrawerVisible);
|
||||
|
||||
// pipeline
|
||||
|
||||
const {
|
||||
visible: pipelineLogSheetVisible,
|
||||
showModal: showPipelineLogSheet,
|
||||
hideModal: hidePipelineLogSheet,
|
||||
} = useSetModalState();
|
||||
|
||||
const {
|
||||
isParsing,
|
||||
logs,
|
||||
messageId,
|
||||
setMessageId,
|
||||
isCompleted,
|
||||
stopFetchTrace,
|
||||
isLogEmpty,
|
||||
} = useFetchPipelineLog(pipelineLogSheetVisible);
|
||||
|
||||
const [uploadedFileData, setUploadedFileData] =
|
||||
useState<Record<string, any>>();
|
||||
const findNodeByName = useGraphStore((state) => state.findNodeByName);
|
||||
|
||||
const handleRunPipeline = useCallback(() => {
|
||||
if (!findNodeByName(Operator.Tokenizer)) {
|
||||
message.warning(t('dataflow.tokenizerRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isParsing) {
|
||||
// show log sheet
|
||||
showPipelineLogSheet();
|
||||
} else {
|
||||
hidePipelineLogSheet();
|
||||
handleRun();
|
||||
}
|
||||
}, [
|
||||
findNodeByName,
|
||||
handleRun,
|
||||
hidePipelineLogSheet,
|
||||
isParsing,
|
||||
showPipelineLogSheet,
|
||||
t,
|
||||
]);
|
||||
|
||||
const { handleCancel } = useCancelCurrentDataflow({
|
||||
messageId,
|
||||
stopFetchTrace,
|
||||
});
|
||||
|
||||
const run = useCallback(() => {
|
||||
if (isPipeline) {
|
||||
handleRunPipeline();
|
||||
} else {
|
||||
handleRunAgent();
|
||||
}
|
||||
}, [handleRunAgent, handleRunPipeline, isPipeline]);
|
||||
|
||||
return (
|
||||
<section className="h-full">
|
||||
<PageHeader>
|
||||
@ -128,7 +194,7 @@ export default function Agent() {
|
||||
>
|
||||
<LaptopMinimalCheck /> {t('flow.save')}
|
||||
</ButtonLoading>
|
||||
<Button variant={'secondary'} onClick={handleRunAgent}>
|
||||
<Button variant={'secondary'} onClick={run}>
|
||||
<CirclePlay />
|
||||
{t('flow.run')}
|
||||
</Button>
|
||||
@ -136,14 +202,15 @@ export default function Agent() {
|
||||
<History />
|
||||
{t('flow.historyversion')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={navigateToAgentLogs(id as string)}
|
||||
>
|
||||
<Logs />
|
||||
{t('flow.log')}
|
||||
</Button>
|
||||
|
||||
{isPipeline || (
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={navigateToAgentLogs(id as string)}
|
||||
>
|
||||
<Logs />
|
||||
{t('flow.log')}
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'secondary'}>
|
||||
@ -160,27 +227,32 @@ export default function Agent() {
|
||||
<Settings />
|
||||
{t('flow.setting')}
|
||||
</AgentDropdownMenuItem>
|
||||
{location.hostname !== 'demo.ragflow.io' && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem onClick={showEmbedModal}>
|
||||
<ScreenShare />
|
||||
{t('common.embedIntoSite')}
|
||||
</AgentDropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
{isPipeline ||
|
||||
(location.hostname !== 'demo.ragflow.io' && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem onClick={showEmbedModal}>
|
||||
<ScreenShare />
|
||||
{t('common.embedIntoSite')}
|
||||
</AgentDropdownMenuItem>
|
||||
</>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<ReactFlowProvider>
|
||||
<DropdownProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
</DropdownProvider>
|
||||
</ReactFlowProvider>
|
||||
<PipelineLogContext.Provider
|
||||
value={{ messageId, setMessageId, setUploadedFileData }}
|
||||
>
|
||||
<ReactFlowProvider>
|
||||
<DropdownProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
</DropdownProvider>
|
||||
</ReactFlowProvider>
|
||||
</PipelineLogContext.Provider>
|
||||
{embedVisible && (
|
||||
<EmbedDialog
|
||||
visible={embedVisible}
|
||||
@ -199,6 +271,19 @@ export default function Agent() {
|
||||
{settingDialogVisible && (
|
||||
<SettingDialog hideModal={hideSettingDialog}></SettingDialog>
|
||||
)}
|
||||
|
||||
{pipelineLogSheetVisible && (
|
||||
<PipelineLogSheet
|
||||
hideModal={hidePipelineLogSheet}
|
||||
isParsing={isParsing}
|
||||
isCompleted={isCompleted}
|
||||
isLogEmpty={isLogEmpty}
|
||||
logs={logs}
|
||||
handleCancel={handleCancel}
|
||||
messageId={messageId}
|
||||
uploadedFileData={uploadedFileData}
|
||||
></PipelineLogSheet>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
137
web/src/pages/agent/pipeline-log-sheet/dataflow-timeline.tsx
Normal file
137
web/src/pages/agent/pipeline-log-sheet/dataflow-timeline.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
Timeline,
|
||||
TimelineContent,
|
||||
TimelineHeader,
|
||||
TimelineIndicator,
|
||||
TimelineItem,
|
||||
TimelineSeparator,
|
||||
TimelineTitle,
|
||||
} from '@/components/originui/timeline';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ITraceData } from '@/interfaces/database/agent';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { File } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export type DataflowTimelineProps = {
|
||||
traceList?: ITraceData[];
|
||||
};
|
||||
|
||||
const END = 'END';
|
||||
|
||||
interface DataflowTrace {
|
||||
datetime: string;
|
||||
elapsed_time: number;
|
||||
message: string;
|
||||
progress: number;
|
||||
timestamp: number;
|
||||
}
|
||||
export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
|
||||
const getNodeData = useCallback(
|
||||
(componentId: string) => {
|
||||
return getNode(componentId)?.data;
|
||||
},
|
||||
[getNode],
|
||||
);
|
||||
|
||||
const getNodeLabel = useCallback(
|
||||
(componentId: string) => {
|
||||
return getNodeData(componentId)?.label as Operator;
|
||||
},
|
||||
[getNodeData],
|
||||
);
|
||||
|
||||
return (
|
||||
<Timeline>
|
||||
{Array.isArray(traceList) &&
|
||||
traceList?.map((item, index) => {
|
||||
const traces = item.trace as DataflowTrace[];
|
||||
const nodeLabel = getNodeLabel(item.component_id);
|
||||
|
||||
const latest = traces[traces.length - 1];
|
||||
const progress = latest.progress * 100;
|
||||
|
||||
return (
|
||||
<TimelineItem
|
||||
key={item.component_id}
|
||||
step={index}
|
||||
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8 pb-6"
|
||||
>
|
||||
<TimelineHeader>
|
||||
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7 bg-accent-primary" />
|
||||
<TimelineTitle className="">
|
||||
<TimelineContent
|
||||
className={cn(
|
||||
'text-foreground rounded-lg border px-4 py-3',
|
||||
)}
|
||||
>
|
||||
<section className="flex items-center justify-between mb-2">
|
||||
<span className="flex-1 truncate">
|
||||
{getNodeData(item.component_id)?.name || END}
|
||||
</span>
|
||||
<div className="flex-1 flex items-center gap-5">
|
||||
<Progress value={progress} className="h-1 flex-1" />
|
||||
<span className="text-accent-primary text-xs">
|
||||
{progress.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
<div className="divide-y space-y-1">
|
||||
{traces
|
||||
.filter((x) => !isEmpty(x.message))
|
||||
.map((x, idx) => (
|
||||
<section
|
||||
key={idx}
|
||||
className="text-text-secondary text-xs space-x-2 py-2.5 !m-0"
|
||||
>
|
||||
<span>{x.datetime}</span>
|
||||
{item.component_id !== 'END' && (
|
||||
<span
|
||||
className={cn({
|
||||
'text-state-error':
|
||||
x.message.startsWith('[ERROR]'),
|
||||
})}
|
||||
>
|
||||
{x.message}
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{x.elapsed_time.toString().slice(0, 6)}s
|
||||
</span>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</TimelineContent>
|
||||
</TimelineTitle>
|
||||
<TimelineIndicator
|
||||
className={cn(
|
||||
'border border-accent-primary group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-5 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||
{
|
||||
'rounded bg-accent-primary': nodeLabel === Operator.Begin,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{item.component_id === END ? (
|
||||
<span className="rounded-full inline-block size-2 bg-accent-primary"></span>
|
||||
) : nodeLabel === Operator.Begin ? (
|
||||
<File className="size-3.5 text-bg-base"></File>
|
||||
) : (
|
||||
<OperatorIcon
|
||||
name={nodeLabel}
|
||||
className="size-3.5 rounded-full"
|
||||
></OperatorIcon>
|
||||
)}
|
||||
</TimelineIndicator>
|
||||
</TimelineHeader>
|
||||
</TimelineItem>
|
||||
);
|
||||
})}
|
||||
</Timeline>
|
||||
);
|
||||
}
|
||||
114
web/src/pages/agent/pipeline-log-sheet/index.tsx
Normal file
114
web/src/pages/agent/pipeline-log-sheet/index.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { SkeletonCard } from '@/components/skeleton-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
CirclePause,
|
||||
Logs,
|
||||
SquareArrowOutUpRight,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import 'react18-json-view/src/style.css';
|
||||
import { useParams } from 'umi';
|
||||
import {
|
||||
isEndOutputEmpty,
|
||||
useDownloadOutput,
|
||||
} from '../hooks/use-download-output';
|
||||
import { UseFetchLogReturnType } from '../hooks/use-fetch-pipeline-log';
|
||||
import { DataflowTimeline } from './dataflow-timeline';
|
||||
|
||||
type LogSheetProps = IModalProps<any> & {
|
||||
handleCancel(): void;
|
||||
uploadedFileData?: Record<string, any>;
|
||||
} & Pick<
|
||||
UseFetchLogReturnType,
|
||||
'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs' | 'messageId'
|
||||
>;
|
||||
|
||||
export function PipelineLogSheet({
|
||||
hideModal,
|
||||
isParsing,
|
||||
logs,
|
||||
handleCancel,
|
||||
isCompleted,
|
||||
isLogEmpty,
|
||||
messageId,
|
||||
uploadedFileData,
|
||||
}: LogSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams();
|
||||
const { data: agent } = useFetchAgent();
|
||||
|
||||
const { handleDownloadJson } = useDownloadOutput(logs);
|
||||
const { navigateToDataflowResult } = useNavigatePage();
|
||||
|
||||
return (
|
||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||
<SheetContent
|
||||
className={cn('top-20 h-auto flex flex-col p-0 gap-0')}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<SheetHeader className="p-5">
|
||||
<SheetTitle className="flex items-center gap-2.5">
|
||||
<Logs className="size-4" /> {t('flow.log')}
|
||||
{isCompleted && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={navigateToDataflowResult({
|
||||
id: messageId, // 'log_id',
|
||||
[PipelineResultSearchParams.AgentId]: id, // 'agent_id',
|
||||
[PipelineResultSearchParams.DocumentId]: uploadedFileData?.id, //'doc_id',
|
||||
[PipelineResultSearchParams.AgentTitle]: agent.title, //'title',
|
||||
[PipelineResultSearchParams.IsReadOnly]: 'true',
|
||||
[PipelineResultSearchParams.Type]: 'dataflow',
|
||||
[PipelineResultSearchParams.CreatedBy]:
|
||||
uploadedFileData?.created_by,
|
||||
[PipelineResultSearchParams.DocumentExtension]:
|
||||
uploadedFileData?.extension,
|
||||
})}
|
||||
>
|
||||
{t('dataflow.viewResult')} <ArrowUpRight />
|
||||
</Button>
|
||||
)}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<section className="flex-1 overflow-auto px-5 pt-5">
|
||||
{isLogEmpty ? (
|
||||
<SkeletonCard className="mt-2" />
|
||||
) : (
|
||||
<DataflowTimeline traceList={logs}></DataflowTimeline>
|
||||
)}
|
||||
</section>
|
||||
<div className="px-5 pb-5">
|
||||
{isParsing ? (
|
||||
<Button
|
||||
className="w-full mt-8 bg-state-error/10 text-state-error hover:bg-state-error hover:text-bg-base"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
<CirclePause /> {t('dataflow.cancel')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleDownloadJson}
|
||||
disabled={isEndOutputEmpty(logs)}
|
||||
className="w-full mt-8 bg-accent-primary-5 text-text-secondary hover:bg-accent-primary-5 hover:text-accent-primary hover:border-accent-primary hover:border"
|
||||
>
|
||||
<SquareArrowOutUpRight />
|
||||
{t('dataflow.exportJson')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@ -14,6 +14,7 @@ import pipe from 'lodash/fp/pipe';
|
||||
import isObject from 'lodash/isObject';
|
||||
import {
|
||||
CategorizeAnchorPointPositions,
|
||||
NoCopyOperatorsList,
|
||||
NoDebugOperatorsList,
|
||||
NodeHandleId,
|
||||
Operator,
|
||||
@ -403,6 +404,10 @@ export const needsSingleStepDebugging = (label: string) => {
|
||||
return !NoDebugOperatorsList.some((x) => (label as Operator) === x);
|
||||
};
|
||||
|
||||
export function showCopyIcon(label: string) {
|
||||
return !NoCopyOperatorsList.some((x) => (label as Operator) === x);
|
||||
}
|
||||
|
||||
// Get the coordinates of the node relative to the Iteration node
|
||||
export function getRelativePositionToIterationNode(
|
||||
nodes: RAGFlowNodeType[],
|
||||
@ -464,7 +469,9 @@ export function convertToStringArray(
|
||||
return list.map((x) => x.value);
|
||||
}
|
||||
|
||||
export function convertToObjectArray(list: Array<string | number | boolean>) {
|
||||
export function convertToObjectArray<T extends string | number | boolean>(
|
||||
list: Array<T>,
|
||||
) {
|
||||
if (!Array.isArray(list)) {
|
||||
return [];
|
||||
}
|
||||
@ -488,7 +495,7 @@ export const buildCategorizeListFromObject = (
|
||||
// Categorize's to field has two data sources, with edges as the data source.
|
||||
// Changes in the edge or to field need to be synchronized to the form field.
|
||||
return Object.keys(categorizeItem)
|
||||
.reduce<Array<ICategorizeItem>>((pre, cur) => {
|
||||
.reduce<Array<Omit<ICategorizeItem, 'uuid'>>>((pre, cur) => {
|
||||
// synchronize edge data to the to field
|
||||
|
||||
pre.push({
|
||||
|
||||
@ -14,7 +14,7 @@ export type DatasetCardProps = {
|
||||
} & Pick<ReturnType<typeof useRenameAgent>, 'showAgentRenameModal'>;
|
||||
|
||||
export function AgentCard({ data, showAgentRenameModal }: DatasetCardProps) {
|
||||
const { navigateToAgent, navigateToDataflow } = useNavigatePage();
|
||||
const { navigateToAgent } = useNavigatePage();
|
||||
|
||||
return (
|
||||
<HomeCard
|
||||
@ -26,9 +26,10 @@ export function AgentCard({ data, showAgentRenameModal }: DatasetCardProps) {
|
||||
}
|
||||
sharedBadge={<SharedBadge>{data.nickname}</SharedBadge>}
|
||||
onClick={
|
||||
data.canvas_category === AgentCategory.DataflowCanvas
|
||||
? navigateToDataflow(data.id)
|
||||
: navigateToAgent(data?.id)
|
||||
// data.canvas_category === AgentCategory.DataflowCanvas
|
||||
// ? navigateToDataflow(data.id)
|
||||
// :
|
||||
navigateToAgent(data?.id, data.canvas_category as AgentCategory)
|
||||
}
|
||||
icon={
|
||||
data.canvas_category === AgentCategory.DataflowCanvas && (
|
||||
|
||||
@ -1,26 +1,16 @@
|
||||
import { IBeginNode } from '@/interfaces/database/flow';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import get from 'lodash/get';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BeginQueryType,
|
||||
BeginQueryTypeIconMap,
|
||||
NodeHandleId,
|
||||
Operator,
|
||||
} from '../../constant';
|
||||
import { BeginQuery } from '../../interface';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
|
||||
// TODO: do not allow other nodes to connect to this node
|
||||
function InnerBeginNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||
const { t } = useTranslation();
|
||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||
|
||||
return (
|
||||
<NodeWrapper selected={selected}>
|
||||
@ -39,22 +29,6 @@ function InnerBeginNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||
{t(`dataflow.begin`)}
|
||||
</div>
|
||||
</section>
|
||||
<section className={cn(styles.generateParameters, 'flex gap-2 flex-col')}>
|
||||
{Object.entries(inputs).map(([key, val], idx) => {
|
||||
const Icon = BeginQueryTypeIconMap[val.type as BeginQueryType];
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={cn(styles.conditionBlock, 'flex gap-1.5 items-center')}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
<label htmlFor="">{key}</label>
|
||||
<span className={styles.parameterValue}>{val.name}</span>
|
||||
<span className="flex-1">{val.optional ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import OperateDropdown from '@/components/operate-dropdown';
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { Flex, MenuProps } from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Operator } from '../../constant';
|
||||
import { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
iconFontColor?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const NodeDropdown = ({ id, iconFontColor, label }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||
const deleteIterationNodeById = useGraphStore(
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode = useCallback(() => {
|
||||
if (label === Operator.Iteration) {
|
||||
deleteIterationNodeById(id);
|
||||
} else {
|
||||
deleteNodeById(id);
|
||||
}
|
||||
}, [label, deleteIterationNodeById, id, deleteNodeById]);
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: '2',
|
||||
onClick: () => duplicateNode(id, label),
|
||||
label: (
|
||||
<Flex justify={'space-between'}>
|
||||
{t('common.copy')}
|
||||
<CopyOutlined />
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<OperateDropdown
|
||||
iconFontSize={22}
|
||||
height={14}
|
||||
deleteItem={deleteNode}
|
||||
items={items}
|
||||
needsDeletionValidation={false}
|
||||
iconFontColor={iconFontColor}
|
||||
></OperateDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeDropdown;
|
||||
@ -1,121 +0,0 @@
|
||||
import get from 'lodash/get';
|
||||
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
|
||||
import JsonView from 'react18-json-view';
|
||||
import 'react18-json-view/src/style.css';
|
||||
import { useReplaceIdWithText } from '../../hooks';
|
||||
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
|
||||
interface IProps extends React.PropsWithChildren {
|
||||
nodeId: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export function NextNodePopover({ children, nodeId, name }: IProps) {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const { data } = useFetchAgent();
|
||||
const { theme } = useTheme();
|
||||
const component = useMemo(() => {
|
||||
return get(data, ['dsl', 'components', nodeId], {});
|
||||
}, [nodeId, data]);
|
||||
|
||||
const inputs: Array<{ component_id: string; content: string }> = get(
|
||||
component,
|
||||
['obj', 'inputs'],
|
||||
[],
|
||||
);
|
||||
const output = get(component, ['obj', 'output'], {});
|
||||
const { replacedOutput } = useReplaceIdWithText(output);
|
||||
const stopPropagation: MouseEventHandler = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const getLabel = useGetComponentLabelByValue(nodeId);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger onClick={stopPropagation} asChild>
|
||||
{children}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align={'start'}
|
||||
side={'right'}
|
||||
sideOffset={20}
|
||||
onClick={stopPropagation}
|
||||
className="w-[400px]"
|
||||
>
|
||||
<div className="mb-3 font-semibold text-[16px]">
|
||||
{name} {t('operationResults')}
|
||||
</div>
|
||||
<div className="flex w-full gap-4 flex-col">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<span className="font-semibold text-[14px]">{t('input')}</span>
|
||||
<div
|
||||
style={
|
||||
theme === 'dark'
|
||||
? {
|
||||
backgroundColor: 'rgba(150, 150, 150, 0.2)',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
className={`bg-gray-100 p-1 rounded`}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('componentId')}</TableHead>
|
||||
<TableHead className="w-[60px]">{t('content')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{inputs.map((x, idx) => (
|
||||
<TableRow key={idx}>
|
||||
<TableCell>{getLabel(x.component_id)}</TableCell>
|
||||
<TableCell className="truncate">{x.content}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<span className="font-semibold text-[14px]">{t('output')}</span>
|
||||
<div
|
||||
style={
|
||||
theme === 'dark'
|
||||
? {
|
||||
backgroundColor: 'rgba(150, 150, 150, 0.2)',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
className="bg-gray-100 p-1 rounded"
|
||||
>
|
||||
<JsonView
|
||||
src={replacedOutput}
|
||||
displaySize={30}
|
||||
className="w-full max-h-[300px] break-words overflow-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -3,24 +3,8 @@ import {
|
||||
initialLlmBaseValues,
|
||||
DataflowOperator as Operator,
|
||||
} from '@/constants/agent';
|
||||
import {
|
||||
ChatVariableEnabledField,
|
||||
variableEnabledFieldMap,
|
||||
} from '@/constants/chat';
|
||||
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
|
||||
export { DataflowOperator as Operator } from '@/constants/agent';
|
||||
|
||||
import {
|
||||
Circle,
|
||||
CircleSlash2,
|
||||
CloudUpload,
|
||||
ListOrdered,
|
||||
OptionIcon,
|
||||
TextCursorInput,
|
||||
ToggleLeft,
|
||||
WrapText,
|
||||
} from 'lucide-react';
|
||||
|
||||
export enum FileType {
|
||||
PDF = 'pdf',
|
||||
Spreadsheet = 'spreadsheet',
|
||||
@ -104,47 +88,8 @@ export enum ContextGeneratorFieldName {
|
||||
Metadata = 'metadata',
|
||||
}
|
||||
|
||||
export enum PromptRole {
|
||||
User = 'user',
|
||||
Assistant = 'assistant',
|
||||
}
|
||||
|
||||
export enum AgentDialogueMode {
|
||||
Conversational = 'conversational',
|
||||
Task = 'task',
|
||||
}
|
||||
|
||||
export const BeginId = 'File';
|
||||
|
||||
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
||||
|
||||
export const CommonOperatorList = Object.values(Operator).filter(
|
||||
(x) => x !== Operator.Note,
|
||||
);
|
||||
|
||||
export const SwitchOperatorOptions = [
|
||||
{ value: '=', label: 'equal', icon: 'equal' },
|
||||
{ value: '≠', label: 'notEqual', icon: 'not-equals' },
|
||||
{ value: '>', label: 'gt', icon: 'Less' },
|
||||
{ value: '≥', label: 'ge', icon: 'Greater-or-equal' },
|
||||
{ value: '<', label: 'lt', icon: 'Less' },
|
||||
{ value: '≤', label: 'le', icon: 'less-or-equal' },
|
||||
{ value: 'contains', label: 'contains', icon: 'Contains' },
|
||||
{ value: 'not contains', label: 'notContains', icon: 'not-contains' },
|
||||
{ value: 'start with', label: 'startWith', icon: 'list-start' },
|
||||
{ value: 'end with', label: 'endWith', icon: 'list-end' },
|
||||
{
|
||||
value: 'empty',
|
||||
label: 'empty',
|
||||
icon: <Circle className="size-4" />,
|
||||
},
|
||||
{
|
||||
value: 'not empty',
|
||||
label: 'notEmpty',
|
||||
icon: <CircleSlash2 className="size-4" />,
|
||||
},
|
||||
];
|
||||
|
||||
export const SwitchElseTo = 'end_cpn_ids';
|
||||
|
||||
export enum TokenizerSearchMethod {
|
||||
@ -186,15 +131,6 @@ export const initialBeginValues = {
|
||||
},
|
||||
};
|
||||
|
||||
export const variableCheckBoxFieldMap = Object.keys(
|
||||
variableEnabledFieldMap,
|
||||
).reduce<Record<string, boolean>>((pre, cur) => {
|
||||
pre[cur] = setInitialChatVariableEnabledFieldValue(
|
||||
cur as ChatVariableEnabledField,
|
||||
);
|
||||
return pre;
|
||||
}, {});
|
||||
|
||||
export const initialNoteValues = {
|
||||
text: '',
|
||||
};
|
||||
@ -341,24 +277,6 @@ export const NodeMap = {
|
||||
[Operator.Extractor]: 'contextNode',
|
||||
};
|
||||
|
||||
export enum BeginQueryType {
|
||||
Line = 'line',
|
||||
Paragraph = 'paragraph',
|
||||
Options = 'options',
|
||||
File = 'file',
|
||||
Integer = 'integer',
|
||||
Boolean = 'boolean',
|
||||
}
|
||||
|
||||
export const BeginQueryTypeIconMap = {
|
||||
[BeginQueryType.Line]: TextCursorInput,
|
||||
[BeginQueryType.Paragraph]: WrapText,
|
||||
[BeginQueryType.Options]: OptionIcon,
|
||||
[BeginQueryType.File]: CloudUpload,
|
||||
[BeginQueryType.Integer]: ListOrdered,
|
||||
[BeginQueryType.Boolean]: ToggleLeft,
|
||||
};
|
||||
|
||||
export const NoDebugOperatorsList = [Operator.Begin];
|
||||
|
||||
export enum NodeHandleId {
|
||||
@ -370,17 +288,6 @@ export enum NodeHandleId {
|
||||
AgentException = 'agentException',
|
||||
}
|
||||
|
||||
export enum VariableType {
|
||||
String = 'string',
|
||||
Array = 'array',
|
||||
File = 'file',
|
||||
}
|
||||
|
||||
export enum AgentExceptionMethod {
|
||||
Comment = 'comment',
|
||||
Goto = 'goto',
|
||||
}
|
||||
|
||||
export const FileTypeSuffixMap = {
|
||||
[FileType.PDF]: ['pdf'],
|
||||
[FileType.Spreadsheet]: ['xls', 'xlsx', 'csv'],
|
||||
|
||||
@ -2,8 +2,7 @@ import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { HandleType, Position } from '@xyflow/react';
|
||||
import { createContext } from 'react';
|
||||
import { useAddNode } from './hooks/use-add-node';
|
||||
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
||||
import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer';
|
||||
import { useShowFormDrawer } from './hooks/use-show-drawer';
|
||||
|
||||
export const AgentFormContext = createContext<RAGFlowNodeType | undefined>(
|
||||
undefined,
|
||||
@ -19,24 +18,6 @@ export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
||||
{} as AgentInstanceContextType,
|
||||
);
|
||||
|
||||
type AgentChatContextType = Pick<
|
||||
ReturnType<typeof useShowLogSheet>,
|
||||
'showLogSheet'
|
||||
> & { setLastSendLoadingFunc: (loading: boolean, messageId: string) => void };
|
||||
|
||||
export const AgentChatContext = createContext<AgentChatContextType>(
|
||||
{} as AgentChatContextType,
|
||||
);
|
||||
|
||||
type AgentChatLogContextType = Pick<
|
||||
ReturnType<typeof useCacheChatLog>,
|
||||
'addEventList' | 'setCurrentMessageId'
|
||||
>;
|
||||
|
||||
export const AgentChatLogContext = createContext<AgentChatLogContextType>(
|
||||
{} as AgentChatLogContextType,
|
||||
);
|
||||
|
||||
export type HandleContextType = {
|
||||
nodeId?: string;
|
||||
id?: string;
|
||||
|
||||
@ -1,260 +0,0 @@
|
||||
import { ButtonLoading } from '@/components/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { IMessage } from '@/pages/chat/interface';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { BeginQueryType } from '../constant';
|
||||
import { BeginQuery } from '../interface';
|
||||
import { FileUploadDirectUpload } from './uploader';
|
||||
|
||||
const StringFields = [
|
||||
BeginQueryType.Line,
|
||||
BeginQueryType.Paragraph,
|
||||
BeginQueryType.Options,
|
||||
];
|
||||
|
||||
interface IProps {
|
||||
parameters: BeginQuery[];
|
||||
message?: IMessage;
|
||||
ok(parameters: any[]): void;
|
||||
isNext?: boolean;
|
||||
loading?: boolean;
|
||||
submitButtonDisabled?: boolean;
|
||||
btnText?: ReactNode;
|
||||
}
|
||||
|
||||
const DebugContent = ({
|
||||
parameters,
|
||||
message,
|
||||
ok,
|
||||
isNext = true,
|
||||
loading = false,
|
||||
submitButtonDisabled = false,
|
||||
btnText,
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchemaValues = useMemo(() => {
|
||||
const obj = parameters.reduce<{
|
||||
schema: Record<string, z.ZodType>;
|
||||
values: Record<string, any>;
|
||||
}>(
|
||||
(pre, cur, idx) => {
|
||||
const type = cur.type;
|
||||
let fieldSchema;
|
||||
let value;
|
||||
if (StringFields.some((x) => x === type)) {
|
||||
fieldSchema = z.string().trim().min(1);
|
||||
} else if (type === BeginQueryType.Boolean) {
|
||||
fieldSchema = z.boolean();
|
||||
value = false;
|
||||
} else if (type === BeginQueryType.Integer || type === 'float') {
|
||||
fieldSchema = z.coerce.number();
|
||||
} else {
|
||||
fieldSchema = z.record(z.any());
|
||||
}
|
||||
|
||||
if (cur.optional) {
|
||||
fieldSchema = fieldSchema.optional();
|
||||
}
|
||||
|
||||
const index = idx.toString();
|
||||
|
||||
pre.schema[index] = fieldSchema;
|
||||
pre.values[index] = value;
|
||||
|
||||
return pre;
|
||||
},
|
||||
{ schema: {}, values: {} },
|
||||
);
|
||||
|
||||
return { schema: z.object(obj.schema), values: obj.values };
|
||||
}, [parameters]);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchemaValues.schema>>({
|
||||
defaultValues: formSchemaValues.values,
|
||||
resolver: zodResolver(formSchemaValues.schema),
|
||||
});
|
||||
|
||||
const submittable = true;
|
||||
|
||||
const renderWidget = useCallback(
|
||||
(q: BeginQuery, idx: string) => {
|
||||
const props = {
|
||||
key: idx,
|
||||
label: q.name ?? q.key,
|
||||
name: idx,
|
||||
};
|
||||
|
||||
const BeginQueryTypeMap = {
|
||||
[BeginQueryType.Line]: (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field}></Input>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[BeginQueryType.Paragraph]: (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={1} {...field}></Textarea>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[BeginQueryType.Options]: (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
allowClear
|
||||
options={
|
||||
q.options?.map((x) => ({
|
||||
label: x,
|
||||
value: x as string,
|
||||
})) ?? []
|
||||
}
|
||||
{...field}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[BeginQueryType.File]: (
|
||||
<React.Fragment key={idx}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<div className="space-y-6">
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>{t('assistantAvatar')}</FormLabel>
|
||||
<FormControl>
|
||||
<FileUploadDirectUpload
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
></FileUploadDirectUpload>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
),
|
||||
[BeginQueryType.Integer]: (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field}></Input>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[BeginQueryType.Boolean]: (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
BeginQueryTypeMap[q.type as BeginQueryType] ??
|
||||
BeginQueryTypeMap[BeginQueryType.Paragraph]
|
||||
);
|
||||
},
|
||||
[form, t],
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(values: z.infer<typeof formSchemaValues.schema>) => {
|
||||
const nextValues = Object.entries(values).map(([key, value]) => {
|
||||
const item = parameters[Number(key)];
|
||||
return { ...item, value };
|
||||
});
|
||||
|
||||
ok(nextValues);
|
||||
},
|
||||
[formSchemaValues, ok, parameters],
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
{message?.data?.tips && <div className="mb-2">{message.data.tips}</div>}
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
{parameters.map((x, idx) => {
|
||||
return <div key={idx}>{renderWidget(x, idx.toString())}</div>;
|
||||
})}
|
||||
<div>
|
||||
<ButtonLoading
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={!submittable || submitButtonDisabled}
|
||||
className="w-full mt-1"
|
||||
>
|
||||
{btnText || t(isNext ? 'common.next' : 'flow.run')}
|
||||
</ButtonLoading>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugContent;
|
||||
@ -1,103 +0,0 @@
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Popover, PopoverContent } from '@/components/ui/popover';
|
||||
import { useParseDocument } from '@/hooks/document-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
const reg =
|
||||
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
|
||||
|
||||
const FormSchema = z.object({
|
||||
url: z.string(),
|
||||
result: z.any(),
|
||||
});
|
||||
|
||||
const values = {
|
||||
url: '',
|
||||
result: null,
|
||||
};
|
||||
|
||||
export const PopoverForm = ({
|
||||
children,
|
||||
visible,
|
||||
switchVisible,
|
||||
}: PropsWithChildren<IModalProps<any>>) => {
|
||||
const form = useForm({
|
||||
defaultValues: values,
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
const { parseDocument, loading } = useParseDocument();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// useResetFormOnCloseModal({
|
||||
// form,
|
||||
// visible,
|
||||
// });
|
||||
|
||||
async function onSubmit(values: z.infer<typeof FormSchema>) {
|
||||
const val = values.url;
|
||||
|
||||
if (reg.test(val)) {
|
||||
const ret = await parseDocument(val);
|
||||
if (ret?.data?.code === 0) {
|
||||
form.setValue('result', ret?.data?.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const content = (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`url`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
// onPressEnter={(e) => e.preventDefault()}
|
||||
placeholder={t('flow.pasteFileLink')}
|
||||
// suffix={
|
||||
// <Button
|
||||
// type="primary"
|
||||
// onClick={onOk}
|
||||
// size={'small'}
|
||||
// loading={loading}
|
||||
// >
|
||||
// {t('common.submit')}
|
||||
// </Button>
|
||||
// }
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`result`}
|
||||
render={() => <></>}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={visible} onOpenChange={switchVisible}>
|
||||
{children}
|
||||
<PopoverContent>{content}</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@ -1,116 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
FileUpload,
|
||||
FileUploadDropzone,
|
||||
FileUploadItem,
|
||||
FileUploadItemDelete,
|
||||
FileUploadItemMetadata,
|
||||
FileUploadItemPreview,
|
||||
FileUploadItemProgress,
|
||||
FileUploadList,
|
||||
FileUploadTrigger,
|
||||
type FileUploadProps,
|
||||
} from '@/components/file-upload';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useUploadCanvasFile } from '@/hooks/use-agent-request';
|
||||
import { Upload, X } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type FileUploadDirectUploadProps = {
|
||||
value: Record<string, any>;
|
||||
onChange(value: Record<string, any>): void;
|
||||
};
|
||||
|
||||
export function FileUploadDirectUpload({
|
||||
onChange,
|
||||
}: FileUploadDirectUploadProps) {
|
||||
const [files, setFiles] = React.useState<File[]>([]);
|
||||
|
||||
const { uploadCanvasFile } = useUploadCanvasFile();
|
||||
|
||||
const onUpload: NonNullable<FileUploadProps['onUpload']> = React.useCallback(
|
||||
async (files, { onSuccess, onError }) => {
|
||||
try {
|
||||
const uploadPromises = files.map(async (file) => {
|
||||
const handleError = (error?: any) => {
|
||||
onError(
|
||||
file,
|
||||
error instanceof Error ? error : new Error('Upload failed'),
|
||||
);
|
||||
};
|
||||
try {
|
||||
const ret = await uploadCanvasFile([file]);
|
||||
if (ret.code === 0) {
|
||||
onSuccess(file);
|
||||
onChange(ret.data);
|
||||
} else {
|
||||
handleError();
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for all uploads to complete
|
||||
await Promise.all(uploadPromises);
|
||||
} catch (error) {
|
||||
// This handles any error that might occur outside the individual upload processes
|
||||
console.error('Unexpected error during upload:', error);
|
||||
}
|
||||
},
|
||||
[onChange, uploadCanvasFile],
|
||||
);
|
||||
|
||||
const onFileReject = React.useCallback((file: File, message: string) => {
|
||||
toast(message, {
|
||||
description: `"${file.name.length > 20 ? `${file.name.slice(0, 20)}...` : file.name}" has been rejected`,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FileUpload
|
||||
value={files}
|
||||
onValueChange={setFiles}
|
||||
onUpload={onUpload}
|
||||
onFileReject={onFileReject}
|
||||
maxFiles={1}
|
||||
className="w-full"
|
||||
multiple={false}
|
||||
>
|
||||
<FileUploadDropzone>
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<div className="flex items-center justify-center rounded-full border p-2.5">
|
||||
<Upload className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="font-medium text-sm">Drag & drop files here</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Or click to browse (max 2 files)
|
||||
</p>
|
||||
</div>
|
||||
<FileUploadTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="mt-2 w-fit">
|
||||
Browse files
|
||||
</Button>
|
||||
</FileUploadTrigger>
|
||||
</FileUploadDropzone>
|
||||
<FileUploadList>
|
||||
{files.map((file, index) => (
|
||||
<FileUploadItem key={index} value={file} className="flex-col">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<FileUploadItemPreview />
|
||||
<FileUploadItemMetadata />
|
||||
<FileUploadItemDelete asChild>
|
||||
<Button variant="ghost" size="icon" className="size-7">
|
||||
<X />
|
||||
</Button>
|
||||
</FileUploadItemDelete>
|
||||
</div>
|
||||
<FileUploadItemProgress />
|
||||
</FileUploadItem>
|
||||
))}
|
||||
</FileUploadList>
|
||||
</FileUpload>
|
||||
);
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Operator, RestrictedUpstreamMap } from './constant';
|
||||
import useGraphStore from './store';
|
||||
|
||||
export const useBuildFormSelectOptions = (
|
||||
operatorName: Operator,
|
||||
selfId?: string, // exclude the current node
|
||||
) => {
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
|
||||
const buildCategorizeToOptions = useCallback(
|
||||
(toList: string[]) => {
|
||||
const excludedNodes: Operator[] = [
|
||||
Operator.Note,
|
||||
...(RestrictedUpstreamMap[operatorName] ?? []),
|
||||
];
|
||||
return nodes
|
||||
.filter(
|
||||
(x) =>
|
||||
excludedNodes.every((y) => y !== x.data.label) &&
|
||||
x.id !== selfId &&
|
||||
!toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options
|
||||
)
|
||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
||||
},
|
||||
[nodes, operatorName, selfId],
|
||||
);
|
||||
|
||||
return buildCategorizeToOptions;
|
||||
};
|
||||
|
||||
export const useBuildSortOptions = () => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const options = useMemo(() => {
|
||||
return ['data', 'relevance'].map((x) => ({
|
||||
value: x,
|
||||
label: t(x),
|
||||
}));
|
||||
}, [t]);
|
||||
return options;
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useOpenDocument() {
|
||||
const openDocument = useCallback(() => {
|
||||
window.open(
|
||||
'https://ragflow.io/docs/dev/category/agent-components',
|
||||
'_blank',
|
||||
);
|
||||
}, []);
|
||||
|
||||
return openDocument;
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
import { useFetchTokenListBeforeOtherStep } from '@/components/embed-dialog/use-show-embed-dialog';
|
||||
import { SharedFrom } from '@/constants/chat';
|
||||
import { useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useCreateSystemToken,
|
||||
useFetchSystemTokenList,
|
||||
useRemoveSystemToken,
|
||||
} from '@/hooks/user-setting-hooks';
|
||||
import { IStats } from '@/interfaces/database/chat';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useOperateApiKey = (idKey: string, dialogId?: string) => {
|
||||
const { removeToken } = useRemoveSystemToken();
|
||||
const { createToken, loading: creatingLoading } = useCreateSystemToken();
|
||||
const { data: tokenList, loading: listLoading } = useFetchSystemTokenList();
|
||||
|
||||
const showDeleteConfirm = useShowDeleteConfirm();
|
||||
|
||||
const onRemoveToken = (token: string) => {
|
||||
showDeleteConfirm({
|
||||
onOk: () => removeToken(token),
|
||||
});
|
||||
};
|
||||
|
||||
const onCreateToken = useCallback(() => {
|
||||
createToken({ [idKey]: dialogId });
|
||||
}, [createToken, idKey, dialogId]);
|
||||
|
||||
return {
|
||||
removeToken: onRemoveToken,
|
||||
createToken: onCreateToken,
|
||||
tokenList,
|
||||
creatingLoading,
|
||||
listLoading,
|
||||
};
|
||||
};
|
||||
|
||||
type ChartStatsType = {
|
||||
[k in keyof IStats]: Array<{ xAxis: string; yAxis: number }>;
|
||||
};
|
||||
|
||||
export const useSelectChartStatsList = (): ChartStatsType => {
|
||||
const queryClient = useQueryClient();
|
||||
const data = queryClient.getQueriesData({ queryKey: ['fetchStats'] });
|
||||
const stats: IStats = (data.length > 0 ? data[0][1] : {}) as IStats;
|
||||
|
||||
return Object.keys(stats).reduce((pre, cur) => {
|
||||
const item = stats[cur as keyof IStats];
|
||||
if (item.length > 0) {
|
||||
pre[cur as keyof IStats] = item.map((x) => ({
|
||||
xAxis: x[0] as string,
|
||||
yAxis: x[1] as number,
|
||||
}));
|
||||
}
|
||||
return pre;
|
||||
}, {} as ChartStatsType);
|
||||
};
|
||||
|
||||
const getUrlWithToken = (token: string, from: string = 'chat') => {
|
||||
const { protocol, host } = window.location;
|
||||
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
||||
};
|
||||
|
||||
export const usePreviewChat = (idKey: string) => {
|
||||
const { handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||
|
||||
const open = useCallback(
|
||||
(t: string) => {
|
||||
window.open(
|
||||
getUrlWithToken(
|
||||
t,
|
||||
idKey === 'canvasId' ? SharedFrom.Agent : SharedFrom.Chat,
|
||||
),
|
||||
'_blank',
|
||||
);
|
||||
},
|
||||
[idKey],
|
||||
);
|
||||
|
||||
const handlePreview = useCallback(async () => {
|
||||
const token = await handleOperate();
|
||||
if (token) {
|
||||
open(token);
|
||||
}
|
||||
}, [handleOperate, open]);
|
||||
|
||||
return {
|
||||
handlePreview,
|
||||
};
|
||||
};
|
||||
@ -4,7 +4,6 @@ import get from 'lodash/get';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { BeginId, Operator } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
import { useCacheChatLog } from './use-cache-chat-log';
|
||||
import { useSaveGraph } from './use-save-graph';
|
||||
|
||||
export const useShowFormDrawer = () => {
|
||||
@ -135,26 +134,6 @@ export function useShowDrawer({
|
||||
};
|
||||
}
|
||||
|
||||
export function useShowLogSheet({
|
||||
setCurrentMessageId,
|
||||
}: Pick<ReturnType<typeof useCacheChatLog>, 'setCurrentMessageId'>) {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
|
||||
const handleShow = useCallback(
|
||||
(messageId: string) => {
|
||||
setCurrentMessageId(messageId);
|
||||
showModal();
|
||||
},
|
||||
[setCurrentMessageId, showModal],
|
||||
);
|
||||
|
||||
return {
|
||||
logSheetVisible: visible,
|
||||
hideLogSheet: hideModal,
|
||||
showLogSheet: handleShow,
|
||||
};
|
||||
}
|
||||
|
||||
export function useHideFormSheetOnNodeDeletion({
|
||||
hideFormDrawer,
|
||||
}: Pick<ReturnType<typeof useShowFormDrawer>, 'hideFormDrawer'>) {
|
||||
|
||||
72
web/src/pages/dataset/dataset-setting/category-panel.tsx
Normal file
72
web/src/pages/dataset/dataset-setting/category-panel.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectParserList } from '@/hooks/user-setting-hooks';
|
||||
import { Col, Divider, Empty, Row, Typography } from 'antd';
|
||||
import DOMPurify from 'dompurify';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import { useMemo } from 'react';
|
||||
import { TagTabs } from './tag-tabs';
|
||||
import { ImageMap } from './utils';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
||||
const parserList = useSelectParserList();
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
|
||||
const item = useMemo(() => {
|
||||
const item = parserList.find((x) => x.value === chunkMethod);
|
||||
if (item) {
|
||||
return {
|
||||
title: item.label,
|
||||
description: t(camelCase(item.value)),
|
||||
};
|
||||
}
|
||||
return { title: '', description: '' };
|
||||
}, [parserList, chunkMethod, t]);
|
||||
|
||||
const imageList = useMemo(() => {
|
||||
if (chunkMethod in ImageMap) {
|
||||
return ImageMap[chunkMethod as keyof typeof ImageMap];
|
||||
}
|
||||
return [];
|
||||
}, [chunkMethod]);
|
||||
|
||||
return (
|
||||
<section>
|
||||
{imageList.length > 0 ? (
|
||||
<>
|
||||
<h5 className="font-semibold text-base mt-0 mb-1">
|
||||
{`"${item.title}" ${t('methodTitle')}`}
|
||||
</h5>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(item.description),
|
||||
}}
|
||||
></p>
|
||||
<h5 className="font-semibold text-base mt-4 mb-1">{`"${item.title}" ${t('methodExamples')}`}</h5>
|
||||
<Text>{t('methodExamplesDescription')}</Text>
|
||||
<Row gutter={[10, 10]} className="mt-4">
|
||||
{imageList.map((x) => (
|
||||
<Col span={12} key={x}>
|
||||
<SvgIcon name={x} width={'100%'} className="w-full"></SvgIcon>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<h5 className="font-semibold text-base mt-4 mb-1">
|
||||
{item.title} {t('dialogueExamplesTitle')}
|
||||
</h5>
|
||||
<Divider></Divider>
|
||||
</>
|
||||
) : (
|
||||
<Empty description={''} image={null}>
|
||||
<p>{t('methodEmpty')}</p>
|
||||
<SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
|
||||
</Empty>
|
||||
)}
|
||||
{chunkMethod === 'tag' && <TagTabs></TagTabs>}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryPanel;
|
||||
@ -0,0 +1,39 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import CategoryPanel from './category-panel';
|
||||
|
||||
export default ({ parserId }: { parserId: string }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={cn('hidden flex-1', 'flex flex-col')}>
|
||||
<div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setVisible(!visible);
|
||||
}}
|
||||
>
|
||||
{t('knowledgeDetails.learnMore')}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="bg-[#FFF]/10 p-[20px] rounded-[12px] mt-[10px] relative flex-1 overflow-auto"
|
||||
style={{ display: visible ? 'block' : 'none' }}
|
||||
>
|
||||
<CategoryPanel chunkMethod={parserId}></CategoryPanel>
|
||||
<div
|
||||
className="absolute right-1 top-1 cursor-pointer hover:text-[#FFF]/30"
|
||||
onClick={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<X />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -21,6 +21,7 @@ import {
|
||||
IGenerateLogButtonProps,
|
||||
} from '../dataset/generate-button/generate';
|
||||
import { ChunkMethodForm } from './chunk-method-form';
|
||||
import ChunkMethodLearnMore from './chunk-method-learn-more';
|
||||
import { IDataPipelineNodeProps } from './components/link-data-pipeline';
|
||||
import { MainContainer } from './configuration-form-container';
|
||||
import { ChunkMethodItem, ParseTypeItem } from './configuration/common-item';
|
||||
@ -169,10 +170,7 @@ export default function DatasetSettings() {
|
||||
></TopTitle>
|
||||
<div className="flex gap-14 flex-1 min-h-0">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 flex-1"
|
||||
>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 ">
|
||||
<div className="w-[768px] h-[calc(100vh-240px)] pr-1 overflow-y-auto scrollbar-auto">
|
||||
<MainContainer className="text-text-secondary">
|
||||
<GeneralForm></GeneralForm>
|
||||
@ -231,6 +229,9 @@ export default function DatasetSettings() {
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="flex-1">
|
||||
{parseType === 1 && <ChunkMethodLearnMore parserId={selectedTag} />}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
305
web/src/pages/dataset/dataset-setting/tag-table/index.tsx
Normal file
305
web/src/pages/dataset/dataset-setting/tag-table/index.tsx
Normal file
@ -0,0 +1,305 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useDeleteTag, useFetchTagList } from '@/hooks/knowledge-hooks';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRenameKnowledgeTag } from '../hooks';
|
||||
import { RenameDialog } from './rename-dialog';
|
||||
|
||||
export type ITag = {
|
||||
tag: string;
|
||||
frequency: number;
|
||||
};
|
||||
|
||||
export function TagTable() {
|
||||
const { t } = useTranslation();
|
||||
const { list } = useFetchTagList();
|
||||
const [tagList, setTagList] = useState<ITag[]>([]);
|
||||
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[],
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
|
||||
const { deleteTag } = useDeleteTag();
|
||||
|
||||
useEffect(() => {
|
||||
setTagList(list.map((x) => ({ tag: x[0], frequency: x[1] })));
|
||||
}, [list]);
|
||||
|
||||
const handleDeleteTag = useCallback(
|
||||
(tags: string[]) => () => {
|
||||
deleteTag(tags);
|
||||
},
|
||||
[deleteTag],
|
||||
);
|
||||
|
||||
const {
|
||||
showTagRenameModal,
|
||||
hideTagRenameModal,
|
||||
tagRenameVisible,
|
||||
initialName,
|
||||
} = useRenameKnowledgeTag();
|
||||
|
||||
const columns: ColumnDef<ITag>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'tag',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
{t('knowledgeConfiguration.tagName')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const value: string = row.getValue('tag');
|
||||
return <div>{value}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'frequency',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
{t('knowledgeConfiguration.frequency')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize ">{row.getValue('frequency')}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
header: t('common.action'),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<Tooltip>
|
||||
<ConfirmDeleteDialog onOk={handleDeleteTag([row.original.tag])}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
</ConfirmDeleteDialog>
|
||||
<TooltipContent>
|
||||
<p>{t('common.delete')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => showTagRenameModal(row.original.tag)}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('common.rename')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: tagList,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
},
|
||||
});
|
||||
|
||||
const selectedRowLength = table.getFilteredSelectedRowModel().rows.length;
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="w-full">
|
||||
<div className="flex items-center justify-between py-4 ">
|
||||
<Input
|
||||
placeholder={t('knowledgeConfiguration.searchTags')}
|
||||
value={(table.getColumn('tag')?.getFilterValue() as string) ?? ''}
|
||||
onChange={(event) =>
|
||||
table.getColumn('tag')?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="w-1/2"
|
||||
/>
|
||||
{selectedRowLength > 0 && (
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDeleteTag(
|
||||
table
|
||||
.getFilteredSelectedRowModel()
|
||||
.rows.map((x) => x.original.tag),
|
||||
)}
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
)}
|
||||
</div>
|
||||
<Table rootClassName="rounded-none border max-h-80 overflow-y-auto">
|
||||
<TableHeader className="bg-[#39393b]">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
|
||||
row(s) selected.
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
{t('common.previousPage')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
{t('common.nextPage')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{tagRenameVisible && (
|
||||
<RenameDialog
|
||||
hideModal={hideTagRenameModal}
|
||||
initialName={initialName}
|
||||
></RenameDialog>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import { useTagIsRenaming } from '@/hooks/knowledge-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RenameForm } from './rename-form';
|
||||
|
||||
export function RenameDialog({
|
||||
hideModal,
|
||||
initialName,
|
||||
}: IModalProps<any> & { initialName: string }) {
|
||||
const { t } = useTranslation();
|
||||
const loading = useTagIsRenaming();
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common.rename')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<RenameForm
|
||||
initialName={initialName}
|
||||
hideModal={hideModal}
|
||||
></RenameForm>
|
||||
<DialogFooter>
|
||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
||||
{t('common.save')}
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useRenameTag } from '@/hooks/knowledge-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function RenameForm({
|
||||
initialName,
|
||||
hideModal,
|
||||
}: IModalProps<any> & { initialName: string }) {
|
||||
const { t } = useTranslation();
|
||||
const FormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.namePlaceholder'),
|
||||
})
|
||||
.trim(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { renameTag } = useRenameTag();
|
||||
|
||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
const ret = await renameTag({ fromTag: initialName, toTag: data.name });
|
||||
if (ret) {
|
||||
hideModal?.();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('name', initialName);
|
||||
}, [form, initialName]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
id={TagRenameId}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('common.namePlaceholder')}
|
||||
{...field}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
40
web/src/pages/dataset/dataset-setting/tag-tabs.tsx
Normal file
40
web/src/pages/dataset/dataset-setting/tag-tabs.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Segmented } from 'antd';
|
||||
import { SegmentedLabeledOption } from 'antd/es/segmented';
|
||||
import { upperFirst } from 'lodash';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TagTable } from './tag-table';
|
||||
import { TagWordCloud } from './tag-word-cloud';
|
||||
|
||||
enum TagType {
|
||||
Cloud = 'cloud',
|
||||
Table = 'table',
|
||||
}
|
||||
|
||||
const TagContentMap = {
|
||||
[TagType.Cloud]: <TagWordCloud></TagWordCloud>,
|
||||
[TagType.Table]: <TagTable></TagTable>,
|
||||
};
|
||||
|
||||
export function TagTabs() {
|
||||
const [value, setValue] = useState<TagType>(TagType.Cloud);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const options: SegmentedLabeledOption[] = [TagType.Cloud, TagType.Table].map(
|
||||
(x) => ({
|
||||
label: t(`knowledgeConfiguration.tag${upperFirst(x)}`),
|
||||
value: x,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="mt-4">
|
||||
<Segmented
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={(val) => setValue(val as TagType)}
|
||||
/>
|
||||
{TagContentMap[value]}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
62
web/src/pages/dataset/dataset-setting/tag-word-cloud.tsx
Normal file
62
web/src/pages/dataset/dataset-setting/tag-word-cloud.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { useFetchTagList } from '@/hooks/knowledge-hooks';
|
||||
import { Chart } from '@antv/g2';
|
||||
import { sumBy } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
export function TagWordCloud() {
|
||||
const domRef = useRef<HTMLDivElement>(null);
|
||||
let chartRef = useRef<Chart>();
|
||||
const { list } = useFetchTagList();
|
||||
|
||||
const { list: tagList } = useMemo(() => {
|
||||
const nextList = list.sort((a, b) => b[1] - a[1]).slice(0, 256);
|
||||
|
||||
return {
|
||||
list: nextList.map((x) => ({ text: x[0], value: x[1], name: x[0] })),
|
||||
sumValue: sumBy(nextList, (x: [string, number]) => x[1]),
|
||||
length: nextList.length,
|
||||
};
|
||||
}, [list]);
|
||||
|
||||
const renderWordCloud = useCallback(() => {
|
||||
if (domRef.current) {
|
||||
chartRef.current = new Chart({ container: domRef.current });
|
||||
|
||||
chartRef.current.options({
|
||||
type: 'wordCloud',
|
||||
autoFit: true,
|
||||
layout: {
|
||||
fontSize: [10, 50],
|
||||
// fontSize: (d: any) => {
|
||||
// if (d.value) {
|
||||
// return (d.value / sumValue) * 100 * (length / 10);
|
||||
// }
|
||||
// return 0;
|
||||
// },
|
||||
},
|
||||
data: {
|
||||
type: 'inline',
|
||||
value: tagList,
|
||||
},
|
||||
encode: { color: 'text' },
|
||||
legend: false,
|
||||
tooltip: {
|
||||
title: 'name', // title
|
||||
items: ['value'], // data item
|
||||
},
|
||||
});
|
||||
|
||||
chartRef.current.render();
|
||||
}
|
||||
}, [tagList]);
|
||||
|
||||
useEffect(() => {
|
||||
renderWordCloud();
|
||||
|
||||
return () => {
|
||||
chartRef.current?.destroy();
|
||||
};
|
||||
}, [renderWordCloud]);
|
||||
|
||||
return <div ref={domRef} className="w-full h-[38vh]"></div>;
|
||||
}
|
||||
20
web/src/pages/dataset/dataset-setting/utils.ts
Normal file
20
web/src/pages/dataset/dataset-setting/utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
const getImageName = (prefix: string, length: number) =>
|
||||
new Array(length)
|
||||
.fill(0)
|
||||
.map((x, idx) => `chunk-method/${prefix}-0${idx + 1}`);
|
||||
|
||||
export const ImageMap = {
|
||||
book: getImageName('book', 4),
|
||||
laws: getImageName('law', 2),
|
||||
manual: getImageName('manual', 4),
|
||||
picture: getImageName('media', 2),
|
||||
naive: getImageName('naive', 2),
|
||||
paper: getImageName('paper', 2),
|
||||
presentation: getImageName('presentation', 2),
|
||||
qa: getImageName('qa', 2),
|
||||
resume: getImageName('resume', 2),
|
||||
table: getImageName('table', 2),
|
||||
one: getImageName('one', 2),
|
||||
knowledge_graph: getImageName('knowledge-graph', 2),
|
||||
tag: getImageName('tag', 2),
|
||||
};
|
||||
Reference in New Issue
Block a user