mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 08:35:08 +08:00
Compare commits
9 Commits
30ae78755b
...
d874683ae4
| Author | SHA1 | Date | |
|---|---|---|---|
| d874683ae4 | |||
| f9e5caa8ed | |||
| 99df0766fe | |||
| 3b50688228 | |||
| ffc095bd50 | |||
| 799c57287c | |||
| eef43fa25c | |||
| 5a4dfecfbe | |||
| 7f237fee16 |
@ -529,8 +529,12 @@ class ComponentBase(ABC):
|
||||
@staticmethod
|
||||
def string_format(content: str, kv: dict[str, str]) -> str:
|
||||
for n, v in kv.items():
|
||||
def repl(_match, val=v):
|
||||
return str(val) if val is not None else ""
|
||||
content = re.sub(
|
||||
r"\{%s\}" % re.escape(n), v, content
|
||||
r"\{%s\}" % re.escape(n),
|
||||
repl,
|
||||
content
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ from api.db.db_models import close_connection
|
||||
from api.db.services import UserService
|
||||
from api.utils import CustomJSONEncoder, commands
|
||||
|
||||
from flask_mail import Mail
|
||||
from flask_session import Session
|
||||
from flask_login import LoginManager
|
||||
from api import settings
|
||||
@ -40,6 +41,7 @@ __all__ = ["app"]
|
||||
Request.json = property(lambda self: self.get_json(force=True, silent=True))
|
||||
|
||||
app = Flask(__name__)
|
||||
smtp_mail_server = Mail()
|
||||
|
||||
# Add this at the beginning of your file to configure Swagger UI
|
||||
swagger_config = {
|
||||
@ -146,16 +148,16 @@ def load_user(web_request):
|
||||
if authorization:
|
||||
try:
|
||||
access_token = str(jwt.loads(authorization))
|
||||
|
||||
|
||||
if not access_token or not access_token.strip():
|
||||
logging.warning("Authentication attempt with empty access token")
|
||||
return None
|
||||
|
||||
|
||||
# Access tokens should be UUIDs (32 hex characters)
|
||||
if len(access_token.strip()) < 32:
|
||||
logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars")
|
||||
return None
|
||||
|
||||
|
||||
user = UserService.query(
|
||||
access_token=access_token, status=StatusEnum.VALID.value
|
||||
)
|
||||
|
||||
@ -29,7 +29,8 @@ from api.db.services.conversation_service import ConversationService, structure_
|
||||
from api.db.services.dialog_service import DialogService, ask, chat
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.llm_service import LLMBundle
|
||||
from api.db.services.user_service import UserTenantService, TenantService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.utils.api_utils import get_data_error_result, get_json_result, server_error_response, validate_request
|
||||
from graphrag.general.mind_map_extractor import MindMapExtractor
|
||||
from rag.app.tag import label_question
|
||||
@ -66,8 +67,14 @@ def set_conversation():
|
||||
e, dia = DialogService.get_by_id(req["dialog_id"])
|
||||
if not e:
|
||||
return get_data_error_result(message="Dialog not found")
|
||||
conv = {"id": conv_id, "dialog_id": req["dialog_id"], "name": name, "message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}],"user_id": current_user.id,
|
||||
"reference":[],}
|
||||
conv = {
|
||||
"id": conv_id,
|
||||
"dialog_id": req["dialog_id"],
|
||||
"name": name,
|
||||
"message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}],
|
||||
"user_id": current_user.id,
|
||||
"reference": [],
|
||||
}
|
||||
ConversationService.save(**conv)
|
||||
return get_json_result(data=conv)
|
||||
except Exception as e:
|
||||
@ -174,6 +181,21 @@ def completion():
|
||||
continue
|
||||
msg.append(m)
|
||||
message_id = msg[-1].get("id")
|
||||
chat_model_id = req.get("llm_id", "")
|
||||
req.pop("llm_id", None)
|
||||
|
||||
chat_model_config = {}
|
||||
for model_config in [
|
||||
"temperature",
|
||||
"top_p",
|
||||
"frequency_penalty",
|
||||
"presence_penalty",
|
||||
"max_tokens",
|
||||
]:
|
||||
config = req.get(model_config)
|
||||
if config:
|
||||
chat_model_config[model_config] = config
|
||||
|
||||
try:
|
||||
e, conv = ConversationService.get_by_id(req["conversation_id"])
|
||||
if not e:
|
||||
@ -190,13 +212,23 @@ def completion():
|
||||
conv.reference = [r for r in conv.reference if r]
|
||||
conv.reference.append({"chunks": [], "doc_aggs": []})
|
||||
|
||||
if chat_model_id:
|
||||
if not TenantLLMService.get_api_key(tenant_id=dia.tenant_id, model_name=chat_model_id):
|
||||
req.pop("chat_model_id", None)
|
||||
req.pop("chat_model_config", None)
|
||||
return get_data_error_result(message=f"Cannot use specified model {chat_model_id}.")
|
||||
dia.llm_id = chat_model_id
|
||||
dia.llm_setting = chat_model_config
|
||||
|
||||
is_embedded = bool(chat_model_id)
|
||||
def stream():
|
||||
nonlocal dia, msg, req, conv
|
||||
try:
|
||||
for ans in chat(dia, msg, True, **req):
|
||||
ans = structure_answer(conv, ans, message_id, conv.id)
|
||||
yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n"
|
||||
ConversationService.update_by_id(conv.id, conv.to_dict())
|
||||
if not is_embedded:
|
||||
ConversationService.update_by_id(conv.id, conv.to_dict())
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n"
|
||||
@ -214,7 +246,8 @@ def completion():
|
||||
answer = None
|
||||
for ans in chat(dia, msg, **req):
|
||||
answer = structure_answer(conv, ans, message_id, conv.id)
|
||||
ConversationService.update_by_id(conv.id, conv.to_dict())
|
||||
if not is_embedded:
|
||||
ConversationService.update_by_id(conv.id, conv.to_dict())
|
||||
break
|
||||
return get_json_result(data=answer)
|
||||
except Exception as e:
|
||||
|
||||
@ -18,12 +18,14 @@ from flask import request
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from api import settings
|
||||
from api.apps import smtp_mail_server
|
||||
from api.db import UserTenantRole, StatusEnum
|
||||
from api.db.db_models import UserTenant
|
||||
from api.db.services.user_service import UserTenantService, UserService
|
||||
|
||||
from api.utils import get_uuid, delta_seconds
|
||||
from api.utils.api_utils import get_json_result, validate_request, server_error_response, get_data_error_result
|
||||
from api.utils.web_utils import send_invite_email
|
||||
|
||||
|
||||
@manager.route("/<tenant_id>/user/list", methods=["GET"]) # noqa: F821
|
||||
@ -78,6 +80,20 @@ def create(tenant_id):
|
||||
role=UserTenantRole.INVITE,
|
||||
status=StatusEnum.VALID.value)
|
||||
|
||||
if smtp_mail_server and settings.SMTP_CONF:
|
||||
from threading import Thread
|
||||
|
||||
user_name = ""
|
||||
_, user = UserService.get_by_id(current_user.id)
|
||||
if user:
|
||||
user_name = user.nickname
|
||||
|
||||
Thread(
|
||||
target=send_invite_email,
|
||||
args=(invite_user_email, settings.MAIL_FRONTEND_URL, tenant_id, user_name or current_user.email),
|
||||
daemon=True
|
||||
).start()
|
||||
|
||||
usr = invite_users[0].to_dict()
|
||||
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
||||
|
||||
|
||||
@ -881,11 +881,12 @@ class Search(DataBaseModel):
|
||||
# chat settings
|
||||
"summary": False,
|
||||
"chat_id": "",
|
||||
# Leave it here for reference, don't need to set default values
|
||||
"llm_setting": {
|
||||
"temperature": 0.1,
|
||||
"top_p": 0.3,
|
||||
"frequency_penalty": 0.7,
|
||||
"presence_penalty": 0.4,
|
||||
# "temperature": 0.1,
|
||||
# "top_p": 0.3,
|
||||
# "frequency_penalty": 0.7,
|
||||
# "presence_penalty": 0.4,
|
||||
},
|
||||
"chat_settingcross_languages": [],
|
||||
"highlight": False,
|
||||
@ -1020,4 +1021,4 @@ def migrate_db():
|
||||
migrate(migrator.add_column("dialog", "meta_data_filter", JSONField(null=True, default={})))
|
||||
except Exception:
|
||||
pass
|
||||
logging.disable(logging.NOTSET)
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@ -99,7 +99,6 @@ class DialogService(CommonService):
|
||||
|
||||
return list(chats.dicts())
|
||||
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_tenant_ids(cls, joined_tenant_ids, user_id, page_number, items_per_page, orderby, desc, keywords, parser_id=None):
|
||||
@ -256,9 +255,10 @@ def repair_bad_citation_formats(answer: str, kbinfos: dict, idx: set):
|
||||
|
||||
def meta_filter(metas: dict, filters: list[dict]):
|
||||
doc_ids = []
|
||||
|
||||
def filter_out(v2docs, operator, value):
|
||||
nonlocal doc_ids
|
||||
for input,docids in v2docs.items():
|
||||
for input, docids in v2docs.items():
|
||||
try:
|
||||
input = float(input)
|
||||
value = float(value)
|
||||
@ -389,7 +389,17 @@ def chat(dialog, messages, stream=True, **kwargs):
|
||||
reasoner = DeepResearcher(
|
||||
chat_mdl,
|
||||
prompt_config,
|
||||
partial(retriever.retrieval, embd_mdl=embd_mdl, tenant_ids=tenant_ids, kb_ids=dialog.kb_ids, page=1, page_size=dialog.top_n, similarity_threshold=0.2, vector_similarity_weight=0.3, doc_ids=attachments),
|
||||
partial(
|
||||
retriever.retrieval,
|
||||
embd_mdl=embd_mdl,
|
||||
tenant_ids=tenant_ids,
|
||||
kb_ids=dialog.kb_ids,
|
||||
page=1,
|
||||
page_size=dialog.top_n,
|
||||
similarity_threshold=0.2,
|
||||
vector_similarity_weight=0.3,
|
||||
doc_ids=attachments,
|
||||
),
|
||||
)
|
||||
|
||||
for think in reasoner.thinking(kbinfos, " ".join(questions)):
|
||||
|
||||
@ -33,7 +33,7 @@ import uuid
|
||||
|
||||
from werkzeug.serving import run_simple
|
||||
from api import settings
|
||||
from api.apps import app
|
||||
from api.apps import app, smtp_mail_server
|
||||
from api.db.runtime_config import RuntimeConfig
|
||||
from api.db.services.document_service import DocumentService
|
||||
from api import utils
|
||||
@ -74,11 +74,11 @@ def signal_handler(sig, frame):
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.info(r"""
|
||||
____ ___ ______ ______ __
|
||||
____ ___ ______ ______ __
|
||||
/ __ \ / | / ____// ____// /____ _ __
|
||||
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
|
||||
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
|
||||
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
|
||||
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
|
||||
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
|
||||
|
||||
""")
|
||||
logging.info(
|
||||
@ -137,6 +137,18 @@ if __name__ == '__main__':
|
||||
else:
|
||||
threading.Timer(1.0, delayed_start_update_progress).start()
|
||||
|
||||
# init smtp server
|
||||
if settings.SMTP_CONF:
|
||||
app.config["MAIL_SERVER"] = settings.MAIL_SERVER
|
||||
app.config["MAIL_PORT"] = settings.MAIL_PORT
|
||||
app.config["MAIL_USE_SSL"] = settings.MAIL_USE_SSL
|
||||
app.config["MAIL_USE_TLS"] = settings.MAIL_USE_TLS
|
||||
app.config["MAIL_USERNAME"] = settings.MAIL_USERNAME
|
||||
app.config["MAIL_PASSWORD"] = settings.MAIL_PASSWORD
|
||||
app.config["MAIL_DEFAULT_SENDER"] = settings.MAIL_DEFAULT_SENDER
|
||||
smtp_mail_server.init_app(app)
|
||||
|
||||
|
||||
# start http server
|
||||
try:
|
||||
logging.info("RAGFlow HTTP server start...")
|
||||
|
||||
@ -79,6 +79,16 @@ STRONG_TEST_COUNT = int(os.environ.get("STRONG_TEST_COUNT", "8"))
|
||||
|
||||
BUILTIN_EMBEDDING_MODELS = ["BAAI/bge-large-zh-v1.5@BAAI", "maidalun1020/bce-embedding-base_v1@Youdao"]
|
||||
|
||||
SMTP_CONF = None
|
||||
MAIL_SERVER = ""
|
||||
MAIL_PORT = 000
|
||||
MAIL_USE_SSL= True
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USERNAME = ""
|
||||
MAIL_PASSWORD = ""
|
||||
MAIL_DEFAULT_SENDER = ()
|
||||
MAIL_FRONTEND_URL = ""
|
||||
|
||||
|
||||
def get_or_create_secret_key():
|
||||
secret_key = os.environ.get("RAGFLOW_SECRET_KEY")
|
||||
@ -186,6 +196,21 @@ def init_settings():
|
||||
global SANDBOX_HOST
|
||||
SANDBOX_HOST = os.environ.get("SANDBOX_HOST", "sandbox-executor-manager")
|
||||
|
||||
global SMTP_CONF, MAIL_SERVER, MAIL_PORT, MAIL_USE_SSL, MAIL_USE_TLS
|
||||
global MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER, MAIL_FRONTEND_URL
|
||||
SMTP_CONF = get_base_config("smtp", {})
|
||||
|
||||
MAIL_SERVER = SMTP_CONF.get("mail_server", "")
|
||||
MAIL_PORT = SMTP_CONF.get("mail_port", 000)
|
||||
MAIL_USE_SSL = SMTP_CONF.get("mail_use_ssl", True)
|
||||
MAIL_USE_TLS = SMTP_CONF.get("mail_use_tls", False)
|
||||
MAIL_USERNAME = SMTP_CONF.get("mail_username", "")
|
||||
MAIL_PASSWORD = SMTP_CONF.get("mail_password", "")
|
||||
mail_default_sender = SMTP_CONF.get("mail_default_sender", [])
|
||||
if mail_default_sender and len(mail_default_sender) >= 2:
|
||||
MAIL_DEFAULT_SENDER = (mail_default_sender[0], mail_default_sender[1])
|
||||
MAIL_FRONTEND_URL = SMTP_CONF.get("mail_frontend_url", "")
|
||||
|
||||
|
||||
class CustomEnum(Enum):
|
||||
@classmethod
|
||||
|
||||
@ -21,6 +21,9 @@ import re
|
||||
import socket
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from api.apps import smtp_mail_server
|
||||
from flask_mail import Message
|
||||
from flask import render_template_string
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
@ -31,6 +34,7 @@ from selenium.webdriver.support.ui import WebDriverWait
|
||||
from webdriver_manager.chrome import ChromeDriverManager
|
||||
|
||||
|
||||
|
||||
CONTENT_TYPE_MAP = {
|
||||
# Office
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
@ -172,3 +176,26 @@ def get_float(req: dict, key: str, default: float | int = 10.0) -> float:
|
||||
return parsed if parsed > 0 else default
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
INVITE_EMAIL_TMPL = """
|
||||
<p>Hi {{email}},</p>
|
||||
<p>{{inviter}} has invited you to join their team (ID: {{tenant_id}}).</p>
|
||||
<p>Click the link below to complete your registration:<br>
|
||||
<a href="{{invite_url}}">{{invite_url}}</a></p>
|
||||
<p>If you did not request this, please ignore this email.</p>
|
||||
"""
|
||||
|
||||
def send_invite_email(to_email, invite_url, tenant_id, inviter):
|
||||
from api.apps import app
|
||||
with app.app_context():
|
||||
msg = Message(subject="RAGFlow Invitation",
|
||||
recipients=[to_email])
|
||||
msg.html = render_template_string(
|
||||
INVITE_EMAIL_TMPL,
|
||||
email=to_email,
|
||||
invite_url=invite_url,
|
||||
tenant_id=tenant_id,
|
||||
inviter=inviter,
|
||||
)
|
||||
smtp_mail_server.send(msg)
|
||||
|
||||
@ -113,3 +113,14 @@ redis:
|
||||
# switch: false
|
||||
# component: false
|
||||
# dataset: false
|
||||
# smtp:
|
||||
# mail_server: ""
|
||||
# mail_port: 465
|
||||
# mail_use_ssl: true
|
||||
# mail_use_tls: false
|
||||
# mail_username: ""
|
||||
# mail_password: ""
|
||||
# mail_default_sender:
|
||||
# - "RAGFlow" # display name
|
||||
# - "" # sender email address
|
||||
# mail_frontend_url: "https://your-frontend.example.com"
|
||||
|
||||
@ -90,9 +90,17 @@ class RAGFlowExcelParser:
|
||||
return wb
|
||||
|
||||
def html(self, fnm, chunk_rows=256):
|
||||
from html import escape
|
||||
|
||||
file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm
|
||||
wb = RAGFlowExcelParser._load_excel_to_workbook(file_like_object)
|
||||
tb_chunks = []
|
||||
|
||||
def _fmt(v):
|
||||
if v is None:
|
||||
return ""
|
||||
return str(v).strip()
|
||||
|
||||
for sheetname in wb.sheetnames:
|
||||
ws = wb[sheetname]
|
||||
rows = list(ws.rows)
|
||||
@ -101,7 +109,7 @@ class RAGFlowExcelParser:
|
||||
|
||||
tb_rows_0 = "<tr>"
|
||||
for t in list(rows[0]):
|
||||
tb_rows_0 += f"<th>{t.value}</th>"
|
||||
tb_rows_0 += f"<th>{escape(_fmt(t.value))}</th>"
|
||||
tb_rows_0 += "</tr>"
|
||||
|
||||
for chunk_i in range((len(rows) - 1) // chunk_rows + 1):
|
||||
@ -109,7 +117,7 @@ class RAGFlowExcelParser:
|
||||
tb += f"<table><caption>{sheetname}</caption>"
|
||||
tb += tb_rows_0
|
||||
for r in list(
|
||||
rows[1 + chunk_i * chunk_rows: 1 + (chunk_i + 1) * chunk_rows]
|
||||
rows[1 + chunk_i * chunk_rows: min(1 + (chunk_i + 1) * chunk_rows, len(rows))]
|
||||
):
|
||||
tb += "<tr>"
|
||||
for i, c in enumerate(r):
|
||||
|
||||
@ -5,7 +5,7 @@ slug: /http_api_reference
|
||||
|
||||
# HTTP API
|
||||
|
||||
A complete reference for RAGFlow's RESTful API. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../guides/models/llm_api_key_setup.md).
|
||||
A complete reference for RAGFlow's RESTful API. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../develop/acquire_ragflow_api_key.md).
|
||||
|
||||
---
|
||||
|
||||
@ -191,13 +191,13 @@ curl --request POST \
|
||||
|
||||
##### Request Parameters
|
||||
|
||||
- `model` (*Body parameter*) `string`, *Required*
|
||||
- `model` (*Body parameter*) `string`, *Required*
|
||||
The model used to generate the response. The server will parse this automatically, so you can set it to any value for now.
|
||||
|
||||
- `messages` (*Body parameter*) `list[object]`, *Required*
|
||||
- `messages` (*Body parameter*) `list[object]`, *Required*
|
||||
A list of historical chat messages used to generate the response. This must contain at least one message with the `user` role.
|
||||
|
||||
- `stream` (*Body parameter*) `boolean`
|
||||
- `stream` (*Body parameter*) `boolean`
|
||||
Whether to receive the response as a stream. Set this to `false` explicitly if you prefer to receive the entire response in one go instead of as a stream.
|
||||
|
||||
#### Response
|
||||
@ -2675,7 +2675,7 @@ curl --request POST \
|
||||
|
||||
- `agent_id`: (*Path parameter*)
|
||||
The ID of the associated agent.
|
||||
- `user_id`: (*Filter parameter*)
|
||||
- `user_id`: (*Filter parameter*)
|
||||
The optional user-defined ID for parsing docs (especially images) when creating a session while uploading files.
|
||||
|
||||
#### Response
|
||||
@ -2755,7 +2755,7 @@ Success:
|
||||
"mode": "conversational",
|
||||
"outputs": {},
|
||||
"prologue": "Hi! I'm your assistant. What can I do for you?",
|
||||
"tips": "Please fill up the form"
|
||||
"tips": "Please fill in the form"
|
||||
}
|
||||
},
|
||||
"upstream": []
|
||||
@ -2912,17 +2912,17 @@ Asks a specified agent a question to start an AI-powered conversation.
|
||||
- Body:
|
||||
- `"question"`: `string`
|
||||
- `"stream"`: `boolean`
|
||||
- `"session_id"`: `string`(optional)
|
||||
- `"inputs"`: `object`(optional)
|
||||
- `"user_id"`: `string`(optional)
|
||||
- `"session_id"`: `string` (optional)
|
||||
- `"inputs"`: `object` (optional)
|
||||
- `"user_id"`: `string` (optional)
|
||||
|
||||
:::info IMPORTANT
|
||||
You can include custom parameters in the request body, but first ensure they are defined in the [Begin](../guides/agent/agent_component_reference/begin.mdx) agent component.
|
||||
You can include custom parameters in the request body, but first ensure they are defined in the [Begin](../guides/agent/agent_component_reference/begin.mdx) component.
|
||||
:::
|
||||
|
||||
##### Request example
|
||||
|
||||
- If the **Begin** component does not take parameters.
|
||||
- If the **Begin** component does not take parameters:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
@ -2936,7 +2936,7 @@ curl --request POST \
|
||||
}'
|
||||
```
|
||||
|
||||
- If the **Begin** component takes parameters.
|
||||
- If the **Begin** component takes parameters, include their values in the body of `"inputs"` as follows:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
|
||||
@ -5,7 +5,7 @@ slug: /python_api_reference
|
||||
|
||||
# Python API
|
||||
|
||||
A complete reference for RAGFlow's Python APIs. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../guides/models/llm_api_key_setup.md).
|
||||
A complete reference for RAGFlow's Python APIs. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../develop/acquire_ragflow_api_key.md).
|
||||
|
||||
:::tip NOTE
|
||||
Run the following command to download the Python SDK:
|
||||
|
||||
@ -22,6 +22,36 @@ The embedding models included in a full edition are:
|
||||
These two embedding models are optimized specifically for English and Chinese, so performance may be compromised if you use them to embed documents in other languages.
|
||||
:::
|
||||
|
||||
## v0.20.2 (Ongoing🔨)
|
||||
|
||||
Released on August ??, 2025.
|
||||
|
||||
### Improvements
|
||||
|
||||
- Revamps the user interface for the **Datasets**, **Chat**, and **Search** pages.
|
||||
- Search: Supports creating search apps tailored to various business scenarios
|
||||
- Chat: Supports comparing answer performance of up to three chat model settings on a single **Chat** page.
|
||||
- Agent:
|
||||
- Implements a toggle in the **Agent** component to enable or disable citation.
|
||||
- Introduces a drag-and-drop method for creating components.
|
||||
- Documentation: Corrects inaccuracies in the API reference.
|
||||
|
||||
### New Agent templates
|
||||
|
||||
- Report Agent: A template for generating summary reports in internal question-answering scenarios, supporting the display of tables and formulae. [#9427](https://github.com/infiniflow/ragflow/pull/9427)
|
||||
|
||||
### Fixed issues
|
||||
|
||||
- Predefined opening greeting in the **Agent** component was missing during conversations.
|
||||
- An automatic line break issue in the prompt editor.
|
||||
- A memory leak issue caused by PyPDF. [#9469](https://github.com/infiniflow/ragflow/pull/9469)
|
||||
|
||||
### API changes
|
||||
|
||||
#### Deprecated
|
||||
|
||||
[Create session with agent](./references/http_api_reference.md#create-session-with-agent)
|
||||
|
||||
## v0.20.1
|
||||
|
||||
Released on August 8, 2025.
|
||||
@ -182,7 +212,7 @@ From this release onwards, if you still see RAGFlow's responses being cut short
|
||||
|
||||
- Unable to add models via Ollama/Xinference, an issue introduced in v0.17.1.
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP APIs
|
||||
|
||||
@ -243,7 +273,7 @@ The following is a screenshot of a conversation that integrates Deep Research:
|
||||
|
||||

|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP APIs
|
||||
|
||||
@ -318,7 +348,7 @@ This release fixes the following issues:
|
||||
- Using the **Table** parsing method results in information loss.
|
||||
- Miscellaneous API issues.
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP APIs
|
||||
|
||||
@ -354,7 +384,7 @@ Released on December 18, 2024.
|
||||
- Upgrades the Document Layout Analysis model in DeepDoc.
|
||||
- Significantly enhances the retrieval performance when using [Infinity](https://github.com/infiniflow/infinity) as document engine.
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP APIs
|
||||
|
||||
@ -411,7 +441,7 @@ This approach eliminates the need to manually update **service_config.yaml** aft
|
||||
Ensure that you [upgrade **both** your code **and** Docker image to this release](https://ragflow.io/docs/dev/upgrade_ragflow#upgrade-ragflow-to-the-most-recent-officially-published-release) before trying this new approach.
|
||||
:::
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP APIs
|
||||
|
||||
@ -570,7 +600,7 @@ While we also test RAGFlow on ARM64 platforms, we do not maintain RAGFlow Docker
|
||||
If you are on an ARM platform, follow [this guide](./develop/build_docker_image.mdx) to build a RAGFlow Docker image.
|
||||
:::
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP API
|
||||
|
||||
@ -591,7 +621,7 @@ Released on May 21, 2024.
|
||||
- Supports monitoring of system components, including Elasticsearch, MySQL, Redis, and MinIO.
|
||||
- Supports disabling **Layout Recognition** in the GENERAL chunking method to reduce file chunking time.
|
||||
|
||||
### Related APIs
|
||||
### API changes
|
||||
|
||||
#### HTTP API
|
||||
|
||||
|
||||
@ -44,9 +44,21 @@ spec:
|
||||
checksum/config-es: {{ include (print $.Template.BasePath "/elasticsearch-config.yaml") . | sha256sum }}
|
||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.elasticsearch.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.elasticsearch.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
- name: fix-data-volume-permissions
|
||||
image: alpine
|
||||
image: {{ .Values.elasticsearch.initContainers.alpine.repository }}:{{ .Values.elasticsearch.initContainers.alpine.tag }}
|
||||
{{- with .Values.elasticsearch.initContainers.alpine.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
@ -55,14 +67,20 @@ spec:
|
||||
- mountPath: /usr/share/elasticsearch/data
|
||||
name: es-data
|
||||
- name: sysctl
|
||||
image: busybox
|
||||
image: {{ .Values.elasticsearch.initContainers.busybox.repository }}:{{ .Values.elasticsearch.initContainers.busybox.tag }}
|
||||
{{- with .Values.elasticsearch.initContainers.busybox.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
privileged: true
|
||||
runAsUser: 0
|
||||
command: ["sysctl", "-w", "vm.max_map_count=262144"]
|
||||
containers:
|
||||
- name: elasticsearch
|
||||
image: elasticsearch:{{ .Values.env.STACK_VERSION }}
|
||||
image: {{ .Values.elasticsearch.image.repository }}:{{ .Values.elasticsearch.image.tag }}
|
||||
{{- with .Values.elasticsearch.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "ragflow.fullname" . }}-env-config
|
||||
|
||||
@ -43,9 +43,21 @@ spec:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.infinity.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.infinity.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: infinity
|
||||
image: {{ .Values.infinity.image.repository }}:{{ .Values.infinity.image.tag }}
|
||||
{{- with .Values.infinity.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "ragflow.fullname" . }}-env-config
|
||||
|
||||
@ -43,9 +43,21 @@ spec:
|
||||
{{- include "ragflow.labels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: minio
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.minio.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.minio.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: minio
|
||||
image: {{ .Values.minio.image.repository }}:{{ .Values.minio.image.tag }}
|
||||
{{- with .Values.minio.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "ragflow.fullname" . }}-env-config
|
||||
|
||||
@ -44,9 +44,21 @@ spec:
|
||||
checksum/config-mysql: {{ include (print $.Template.BasePath "/mysql-config.yaml") . | sha256sum }}
|
||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.mysql.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.mysql.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: mysql
|
||||
image: {{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }}
|
||||
{{- with .Values.mysql.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "ragflow.fullname" . }}-env-config
|
||||
|
||||
@ -44,9 +44,21 @@ spec:
|
||||
checksum/config-opensearch: {{ include (print $.Template.BasePath "/opensearch-config.yaml") . | sha256sum }}
|
||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.opensearch.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.opensearch.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
- name: fix-data-volume-permissions
|
||||
image: alpine
|
||||
image: {{ .Values.opensearch.initContainers.alpine.repository }}:{{ .Values.opensearch.initContainers.alpine.tag }}
|
||||
{{- with .Values.opensearch.initContainers.alpine.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
@ -55,7 +67,10 @@ spec:
|
||||
- mountPath: /usr/share/opensearch/data
|
||||
name: opensearch-data
|
||||
- name: sysctl
|
||||
image: busybox
|
||||
image: {{ .Values.opensearch.initContainers.busybox.repository }}:{{ .Values.opensearch.initContainers.busybox.tag }}
|
||||
{{- with .Values.opensearch.initContainers.busybox.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
privileged: true
|
||||
runAsUser: 0
|
||||
@ -63,6 +78,9 @@ spec:
|
||||
containers:
|
||||
- name: opensearch
|
||||
image: {{ .Values.opensearch.image.repository }}:{{ .Values.opensearch.image.tag }}
|
||||
{{- with .Values.opensearch.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "ragflow.fullname" . }}-env-config
|
||||
|
||||
@ -25,9 +25,21 @@ spec:
|
||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
checksum/config-ragflow: {{ include (print $.Template.BasePath "/ragflow_config.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.ragflow.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.ragflow.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: ragflow
|
||||
image: {{ .Values.env.RAGFLOW_IMAGE }}
|
||||
image: {{ .Values.ragflow.image.repository }}:{{ .Values.ragflow.image.tag }}
|
||||
{{- with .Values.ragflow.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
|
||||
@ -40,10 +40,22 @@ spec:
|
||||
annotations:
|
||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||
spec:
|
||||
{{- if or .Values.imagePullSecrets .Values.redis.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.redis.image.pullSecrets }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- name: redis
|
||||
image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}
|
||||
{{- with .Values.redis.image.pullPolicy }}
|
||||
imagePullPolicy: {{ . }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "sh"
|
||||
- "-c"
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
# Based on docker compose .env file
|
||||
|
||||
# Global image pull secrets configuration
|
||||
imagePullSecrets: []
|
||||
|
||||
env:
|
||||
# The type of doc engine to use.
|
||||
# Available options:
|
||||
@ -32,31 +36,6 @@ env:
|
||||
# The password for Redis
|
||||
REDIS_PASSWORD: infini_rag_flow_helm
|
||||
|
||||
# The RAGFlow Docker image to download.
|
||||
# Defaults to the v0.20.1-slim edition, which is the RAGFlow Docker image without embedding models.
|
||||
RAGFLOW_IMAGE: infiniflow/ragflow:v0.20.1-slim
|
||||
#
|
||||
# To download the RAGFlow Docker image with embedding models, uncomment the following line instead:
|
||||
# RAGFLOW_IMAGE: infiniflow/ragflow:v0.20.1
|
||||
#
|
||||
# The Docker image of the v0.20.1 edition includes:
|
||||
# - Built-in embedding models:
|
||||
# - BAAI/bge-large-zh-v1.5
|
||||
# - BAAI/bge-reranker-v2-m3
|
||||
# - maidalun1020/bce-embedding-base_v1
|
||||
# - maidalun1020/bce-reranker-base_v1
|
||||
# - Embedding models that will be downloaded once you select them in the RAGFlow UI:
|
||||
# - BAAI/bge-base-en-v1.5
|
||||
# - BAAI/bge-large-en-v1.5
|
||||
# - BAAI/bge-small-en-v1.5
|
||||
# - BAAI/bge-small-zh-v1.5
|
||||
# - jinaai/jina-embeddings-v2-base-en
|
||||
# - jinaai/jina-embeddings-v2-small-en
|
||||
# - nomic-ai/nomic-embed-text-v1.5
|
||||
# - sentence-transformers/all-MiniLM-L6-v2
|
||||
#
|
||||
#
|
||||
|
||||
# The local time zone.
|
||||
TIMEZONE: "Asia/Shanghai"
|
||||
|
||||
@ -75,7 +54,11 @@ env:
|
||||
EMBEDDING_BATCH_SIZE: 16
|
||||
|
||||
ragflow:
|
||||
|
||||
image:
|
||||
repository: infiniflow/ragflow
|
||||
tag: v0.20.1-slim
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
# Optional service configuration overrides
|
||||
# to be written to local.service_conf.yaml
|
||||
# inside the RAGFlow container
|
||||
@ -114,6 +97,8 @@ infinity:
|
||||
image:
|
||||
repository: infiniflow/infinity
|
||||
tag: v0.6.0-dev5
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
storage:
|
||||
className:
|
||||
capacity: 5Gi
|
||||
@ -124,6 +109,20 @@ infinity:
|
||||
type: ClusterIP
|
||||
|
||||
elasticsearch:
|
||||
image:
|
||||
repository: elasticsearch
|
||||
tag: "8.11.3"
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
initContainers:
|
||||
alpine:
|
||||
repository: alpine
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
busybox:
|
||||
repository: busybox
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
storage:
|
||||
className:
|
||||
capacity: 20Gi
|
||||
@ -140,6 +139,17 @@ opensearch:
|
||||
image:
|
||||
repository: opensearchproject/opensearch
|
||||
tag: 2.19.1
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
initContainers:
|
||||
alpine:
|
||||
repository: alpine
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
busybox:
|
||||
repository: busybox
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
storage:
|
||||
className:
|
||||
capacity: 20Gi
|
||||
@ -156,6 +166,8 @@ minio:
|
||||
image:
|
||||
repository: quay.io/minio/minio
|
||||
tag: RELEASE.2023-12-20T01-00-02Z
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
storage:
|
||||
className:
|
||||
capacity: 5Gi
|
||||
@ -169,6 +181,8 @@ mysql:
|
||||
image:
|
||||
repository: mysql
|
||||
tag: 8.0.39
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
storage:
|
||||
className:
|
||||
capacity: 5Gi
|
||||
@ -182,6 +196,8 @@ redis:
|
||||
image:
|
||||
repository: valkey/valkey
|
||||
tag: 8
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
storage:
|
||||
className:
|
||||
capacity: 5Gi
|
||||
|
||||
@ -130,6 +130,7 @@ dependencies = [
|
||||
"click>=8.1.8",
|
||||
"python-calamine>=0.4.0",
|
||||
"litellm>=1.74.15.post1",
|
||||
"flask-mail>=0.10.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@ -490,6 +490,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
|
||||
sections = [(_, "") for _ in excel_parser.html(binary, 12) if _]
|
||||
else:
|
||||
sections = [(_, "") for _ in excel_parser(binary) if _]
|
||||
parser_config["chunk_token_num"] = 12800
|
||||
|
||||
elif re.search(r"\.(txt|py|js|java|c|cpp|h|php|go|ts|sh|cs|kt|sql)$", filename, re.IGNORECASE):
|
||||
callback(0.1, "Start to parse.")
|
||||
|
||||
26
uv.lock
generated
26
uv.lock
generated
@ -1,4 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.10, <3.13"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12' and sys_platform == 'darwin'",
|
||||
@ -1189,9 +1190,6 @@ name = "datrie"
|
||||
version = "0.8.2"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/9d/fe/db74bd405d515f06657f11ad529878fd389576dca4812bea6f98d9b31574/datrie-0.8.2.tar.gz", hash = "sha256:525b08f638d5cf6115df6ccd818e5a01298cd230b2dac91c8ff2e6499d18765d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/44/02/53f0cf0bf0cd629ba6c2cc13f2f9db24323459e9c19463783d890a540a96/datrie-0.8.2-pp273-pypy_73-win32.whl", hash = "sha256:b07bd5fdfc3399a6dab86d6e35c72b1dbd598e80c97509c7c7518ab8774d3fda" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugpy"
|
||||
@ -1653,6 +1651,19 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-mail"
|
||||
version = "0.10.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "blinker" },
|
||||
{ name = "flask" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/ba/29/e92dc84c675d1e8d260d5768eb3fb65c70cbd33addecf424187587bee862/flask_mail-0.10.0.tar.gz", hash = "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e4/c0/a81083da779f482494d49195d8b6c9fde21072558253e4a9fb2ec969c3c1/flask_mail-0.10.0-py3-none-any.whl", hash = "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-session"
|
||||
version = "0.8.0"
|
||||
@ -4664,8 +4675,6 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/7c/f5b0556590e7b4e710509105e668adb55aa9470a9f0e4dea9c40a4a11ce1/pycryptodome-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:350ebc1eba1da729b35ab7627a833a1a355ee4e852d8ba0447fafe7b14504d56" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/33/38/dcc795578d610ea1aaffef4b148b8cafcfcf4d126b1e58231ddc4e475c70/pycryptodome-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:93837e379a3e5fd2bb00302a47aee9fdf7940d83595be3915752c74033d17ca7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630" },
|
||||
@ -4689,8 +4698,6 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/48/7d/0f2b09490b98cc6a902ac15dda8760c568b9c18cfe70e0ef7a16de64d53a/pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b0/1c/375adb14b71ee1c8d8232904e928b3e7af5bbbca7c04e4bec94fe8e90c3d/pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b2/e8/1b92184ab7e5595bf38000587e6f8cf9556ebd1bf0a583619bee2057afbd/pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e7/c5/9140bb867141d948c8e242013ec8a8011172233c898dfdba0a2417c3169a/pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5e/6a/04acb4978ce08ab16890c70611ebc6efd251681341617bbb9e53356dee70/pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/eb/df/3f1ea084e43b91e6d2b6b3493cc948864c17ea5d93ff1261a03812fbfd1a/pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/f3/83ffbdfa0c8f9154bcd8866895f6cae5a3ec749da8b0840603cf936c4412/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/9d/c113e640aaf02af5631ae2686b742aac5cd0e1402b9d6512b1c7ec5ef05d/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781" },
|
||||
@ -5293,6 +5300,7 @@ dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-cors" },
|
||||
{ name = "flask-login" },
|
||||
{ name = "flask-mail" },
|
||||
{ name = "flask-session" },
|
||||
{ name = "google-generativeai" },
|
||||
{ name = "google-search-results" },
|
||||
@ -5447,6 +5455,7 @@ requires-dist = [
|
||||
{ name = "flask", specifier = "==3.0.3" },
|
||||
{ name = "flask-cors", specifier = "==5.0.0" },
|
||||
{ name = "flask-login", specifier = "==0.6.3" },
|
||||
{ name = "flask-mail", specifier = ">=0.10.0" },
|
||||
{ name = "flask-session", specifier = "==0.8.0" },
|
||||
{ name = "google-generativeai", specifier = ">=0.8.1,<0.9.0" },
|
||||
{ name = "google-search-results", specifier = "==2.4.2" },
|
||||
@ -5492,7 +5501,7 @@ requires-dist = [
|
||||
{ name = "pyicu", specifier = ">=2.13.1,<3.0.0" },
|
||||
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
||||
{ name = "pyodbc", specifier = ">=5.2.0,<6.0.0" },
|
||||
{ name = "pypdf", specifier = "===6.0.0" },
|
||||
{ name = "pypdf", specifier = "==6.0.0" },
|
||||
{ name = "pypdf2", specifier = ">=3.0.1,<4.0.0" },
|
||||
{ name = "python-calamine", specifier = ">=0.4.0" },
|
||||
{ name = "python-dateutil", specifier = "==2.8.2" },
|
||||
@ -5539,6 +5548,7 @@ requires-dist = [
|
||||
{ name = "yfinance", specifier = "==0.2.65" },
|
||||
{ name = "zhipuai", specifier = "==2.0.1" },
|
||||
]
|
||||
provides-extras = ["full"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
test = [
|
||||
|
||||
@ -70,6 +70,10 @@ const KnowledgeBaseItem = ({
|
||||
|
||||
export default KnowledgeBaseItem;
|
||||
|
||||
function buildQueryVariableOptionsByShowVariable(showVariable?: boolean) {
|
||||
return showVariable ? useBuildQueryVariableOptions : () => [];
|
||||
}
|
||||
|
||||
export function KnowledgeBaseFormField({
|
||||
showVariable = false,
|
||||
}: {
|
||||
@ -84,7 +88,7 @@ export function KnowledgeBaseFormField({
|
||||
(x) => x.parser_id !== DocumentParserType.Tag,
|
||||
);
|
||||
|
||||
const nextOptions = useBuildQueryVariableOptions();
|
||||
const nextOptions = buildQueryVariableOptionsByShowVariable(showVariable)();
|
||||
|
||||
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
||||
label: x.name,
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
useFetchDocumentInfosByIds,
|
||||
@ -12,17 +11,13 @@ import {
|
||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
import { IMessage } from '@/pages/chat/interface';
|
||||
import MarkdownContent from '@/pages/chat/markdown-content';
|
||||
import { getExtension, isImage } from '@/utils/document-util';
|
||||
import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
||||
import FileIcon from '../file-icon';
|
||||
import IndentedTreeModal from '../indented-tree/modal';
|
||||
import NewDocumentLink from '../new-document-link';
|
||||
import { Avatar, Flex, Space } from 'antd';
|
||||
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
||||
import { useTheme } from '../theme-provider';
|
||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
|
||||
item: IMessage;
|
||||
reference: IReference;
|
||||
@ -59,21 +54,11 @@ const MessageItem = ({
|
||||
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
||||
const { data: documentThumbnails, setDocumentIds: setIds } =
|
||||
useFetchDocumentThumbnailsByIds();
|
||||
const { visible, hideModal, showModal } = useSetModalState();
|
||||
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
||||
|
||||
const referenceDocumentList = useMemo(() => {
|
||||
return reference?.doc_aggs ?? [];
|
||||
}, [reference?.doc_aggs]);
|
||||
|
||||
const handleUserDocumentClick = useCallback(
|
||||
(id: string) => () => {
|
||||
setClickedDocumentId(id);
|
||||
showModal();
|
||||
},
|
||||
[showModal],
|
||||
);
|
||||
|
||||
const handleRegenerateMessage = useCallback(() => {
|
||||
regenerateMessage?.(item);
|
||||
}, [regenerateMessage, item]);
|
||||
@ -160,83 +145,18 @@ const MessageItem = ({
|
||||
></MarkdownContent>
|
||||
</div>
|
||||
{isAssistant && referenceDocumentList.length > 0 && (
|
||||
<List
|
||||
bordered
|
||||
dataSource={referenceDocumentList}
|
||||
renderItem={(item) => {
|
||||
return (
|
||||
<List.Item>
|
||||
<Flex gap={'small'} align="center">
|
||||
<FileIcon
|
||||
id={item.doc_id}
|
||||
name={item.doc_name}
|
||||
></FileIcon>
|
||||
|
||||
<NewDocumentLink
|
||||
documentId={item.doc_id}
|
||||
documentName={item.doc_name}
|
||||
prefix="document"
|
||||
link={item.url}
|
||||
>
|
||||
{item.doc_name}
|
||||
</NewDocumentLink>
|
||||
</Flex>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<ReferenceDocumentList
|
||||
list={referenceDocumentList}
|
||||
></ReferenceDocumentList>
|
||||
)}
|
||||
{isUser && documentList.length > 0 && (
|
||||
<List
|
||||
bordered
|
||||
dataSource={documentList}
|
||||
renderItem={(item) => {
|
||||
// TODO:
|
||||
// const fileThumbnail =
|
||||
// documentThumbnails[item.id] || documentThumbnails[item.id];
|
||||
const fileExtension = getExtension(item.name);
|
||||
return (
|
||||
<List.Item>
|
||||
<Flex gap={'small'} align="center">
|
||||
<FileIcon id={item.id} name={item.name}></FileIcon>
|
||||
|
||||
{isImage(fileExtension) ? (
|
||||
<NewDocumentLink
|
||||
documentId={item.id}
|
||||
documentName={item.name}
|
||||
prefix="document"
|
||||
>
|
||||
{item.name}
|
||||
</NewDocumentLink>
|
||||
) : (
|
||||
<Button
|
||||
type={'text'}
|
||||
onClick={handleUserDocumentClick(item.id)}
|
||||
>
|
||||
<Text
|
||||
style={{ maxWidth: '40vw' }}
|
||||
ellipsis={{ tooltip: item.name }}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<InnerUploadedMessageFiles
|
||||
files={documentList}
|
||||
></InnerUploadedMessageFiles>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</section>
|
||||
{visible && (
|
||||
<IndentedTreeModal
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={clickedDocumentId}
|
||||
></IndentedTreeModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -116,64 +116,6 @@ export const AssistantGroupButton = ({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Radio.Group size="small">
|
||||
<Radio.Button value="a">
|
||||
<CopyToClipboard text={content}></CopyToClipboard>
|
||||
</Radio.Button>
|
||||
{showLoudspeaker && (
|
||||
<Radio.Button value="b" onClick={handleRead}>
|
||||
<Tooltip title={t('chat.read')}>
|
||||
{isPlaying ? <PauseCircleOutlined /> : <SoundOutlined />}
|
||||
</Tooltip>
|
||||
<audio src="" ref={ref}></audio>
|
||||
</Radio.Button>
|
||||
)}
|
||||
{showLikeButton && (
|
||||
<>
|
||||
<Radio.Button value="c" onClick={handleLike}>
|
||||
<LikeOutlined />
|
||||
</Radio.Button>
|
||||
<Radio.Button value="d" onClick={showModal}>
|
||||
<DislikeOutlined />
|
||||
</Radio.Button>
|
||||
</>
|
||||
)}
|
||||
{prompt && (
|
||||
<Radio.Button value="e" onClick={showPromptModal}>
|
||||
<PromptIcon style={{ fontSize: '16px' }} />
|
||||
</Radio.Button>
|
||||
)}
|
||||
<Radio.Button
|
||||
value="f"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleShowLogSheet();
|
||||
}}
|
||||
>
|
||||
<NotebookText className="size-4" />
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
{visible && (
|
||||
<FeedbackModal
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
onOk={onFeedbackOk}
|
||||
loading={loading}
|
||||
></FeedbackModal>
|
||||
)}
|
||||
{promptVisible && (
|
||||
<PromptModal
|
||||
visible={promptVisible}
|
||||
hideModal={hidePromptModal}
|
||||
prompt={prompt}
|
||||
></PromptModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface UserGroupButtonProps extends Partial<IRemoveMessageById> {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { IReferenceChunk, IReferenceObject } from '@/interfaces/database/chat';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
@ -21,7 +20,6 @@ import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
||||
import { IMessage } from '@/pages/chat/interface';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import IndentedTreeModal from '../indented-tree/modal';
|
||||
import MarkdownContent from '../next-markdown-content';
|
||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||
import { useTheme } from '../theme-provider';
|
||||
@ -79,8 +77,6 @@ function MessageItem({
|
||||
const { theme } = useTheme();
|
||||
const isAssistant = item.role === MessageType.Assistant;
|
||||
const isUser = item.role === MessageType.User;
|
||||
const { visible, hideModal } = useSetModalState();
|
||||
const [clickedDocumentId] = useState('');
|
||||
const [showThinking, setShowThinking] = useState(false);
|
||||
const { setLastSendLoadingFunc } = useContext(AgentChatContext);
|
||||
|
||||
@ -200,8 +196,6 @@ function MessageItem({
|
||||
sendLoading={sendLoading}
|
||||
></UserGroupButton>
|
||||
)}
|
||||
|
||||
{/* <b>{isAssistant ? '' : nickname}</b> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -254,13 +248,6 @@ function MessageItem({
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
{visible && (
|
||||
<IndentedTreeModal
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={clickedDocumentId}
|
||||
></IndentedTreeModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ export function ReferenceDocumentList({ list }: { list: Docagg[] }) {
|
||||
<section className="flex gap-3 flex-wrap">
|
||||
{list.map((item) => (
|
||||
<Card key={item.doc_id}>
|
||||
<CardContent className="p-2">
|
||||
<CardContent className="p-2 space-x-2">
|
||||
<FileIcon id={item.doc_id} name={item.doc_name}></FileIcon>
|
||||
<NewDocumentLink
|
||||
documentId={item.doc_id}
|
||||
|
||||
@ -1,34 +1,65 @@
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { formatBytes } from '@/utils/file-util';
|
||||
import { memo } from 'react';
|
||||
import FileIcon from '../file-icon';
|
||||
import NewDocumentLink from '../new-document-link';
|
||||
import SvgIcon from '../svg-icon';
|
||||
|
||||
interface IProps {
|
||||
files?: File[];
|
||||
files?: File[] | IDocumentInfo[];
|
||||
}
|
||||
|
||||
type NameWidgetType = {
|
||||
name: string;
|
||||
size: number;
|
||||
id?: string;
|
||||
};
|
||||
function NameWidget({ name, size, id }: NameWidgetType) {
|
||||
return (
|
||||
<div className="text-xs max-w-20">
|
||||
{id ? (
|
||||
<NewDocumentLink documentId={id} documentName={name} prefix="document">
|
||||
{name}
|
||||
</NewDocumentLink>
|
||||
) : (
|
||||
<div className="truncate">{name}</div>
|
||||
)}
|
||||
<p className="text-text-secondary pt-1">{formatBytes(size)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export function InnerUploadedMessageFiles({ files = [] }: IProps) {
|
||||
return (
|
||||
<section className="flex gap-2 pt-2">
|
||||
{files?.map((file, idx) => (
|
||||
<div key={idx} className="flex gap-1 border rounded-md p-1.5">
|
||||
{file.type.startsWith('image/') ? (
|
||||
<img
|
||||
src={URL.createObjectURL(file)}
|
||||
alt={file.name}
|
||||
className="size-10 object-cover"
|
||||
/>
|
||||
) : (
|
||||
<SvgIcon
|
||||
name={`file-icon/${getExtension(file.name)}`}
|
||||
width={24}
|
||||
></SvgIcon>
|
||||
)}
|
||||
<div className="text-xs max-w-20">
|
||||
<div className="truncate">{file.name}</div>
|
||||
<p className="text-text-secondary pt-1">{formatBytes(file.size)}</p>
|
||||
{files?.map((file, idx) => {
|
||||
const name = file.name;
|
||||
const isFile = file instanceof File;
|
||||
|
||||
return (
|
||||
<div key={idx} className="flex gap-1 border rounded-md p-1.5">
|
||||
{!isFile ? (
|
||||
<FileIcon id={file.id} name={name}></FileIcon>
|
||||
) : file.type.startsWith('image/') ? (
|
||||
<img
|
||||
src={URL.createObjectURL(file)}
|
||||
alt={name}
|
||||
className="size-10 object-cover"
|
||||
/>
|
||||
) : (
|
||||
<SvgIcon
|
||||
name={`file-icon/${getExtension(name)}`}
|
||||
width={24}
|
||||
></SvgIcon>
|
||||
)}
|
||||
<NameWidget
|
||||
name={name}
|
||||
size={file.size}
|
||||
id={isFile ? undefined : file.id}
|
||||
></NameWidget>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ export const variableEnabledFieldMap = {
|
||||
export enum SharedFrom {
|
||||
Agent = 'agent',
|
||||
Chat = 'chat',
|
||||
Search = 'search',
|
||||
}
|
||||
|
||||
export enum ChatSearchParams {
|
||||
|
||||
@ -25,7 +25,7 @@ import { useDebounce } from 'ahooks';
|
||||
import { get, set } from 'lodash';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
@ -304,6 +304,9 @@ export const useSetAgent = (showMessage: boolean = true) => {
|
||||
// Only one file can be uploaded at a time
|
||||
export const useUploadCanvasFile = () => {
|
||||
const { id } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const canvasId = id || shared_id;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
@ -321,7 +324,7 @@ export const useUploadCanvasFile = () => {
|
||||
}
|
||||
|
||||
const { data } = await agentService.uploadCanvasFile(
|
||||
{ url: api.uploadAgentFile(id), data: nextBody },
|
||||
{ url: api.uploadAgentFile(canvasId as string), data: nextBody },
|
||||
true,
|
||||
);
|
||||
if (data?.code === 0) {
|
||||
|
||||
@ -57,6 +57,18 @@ export interface IDialog {
|
||||
similarity_threshold: number;
|
||||
top_k: number;
|
||||
top_n: number;
|
||||
meta_data_filter: MetaDataFilter;
|
||||
}
|
||||
|
||||
interface MetaDataFilter {
|
||||
manual: Manual[];
|
||||
method: string;
|
||||
}
|
||||
|
||||
interface Manual {
|
||||
key: string;
|
||||
op: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IConversation {
|
||||
|
||||
@ -1418,6 +1418,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
||||
},
|
||||
search: {
|
||||
createSearch: 'Create Search',
|
||||
searchGreeting: 'How can I help you today ?',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1185,9 +1185,13 @@ export default {
|
||||
knowledge: '知識',
|
||||
chat: '聊天',
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
okText: '確認',
|
||||
cancelText: '取消',
|
||||
modal: {
|
||||
okText: '確認',
|
||||
cancelText: '取消',
|
||||
},
|
||||
search: {
|
||||
createSearch: '新建查詢',
|
||||
searchGreeting: '今天我能為你做些什麽?',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1316,12 +1316,13 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
},
|
||||
search: {
|
||||
createSearch: '新建查询',
|
||||
modal: {
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
},
|
||||
search: {
|
||||
createSearch: '新建查询',
|
||||
searchGreeting: '今天我能为你做些什么?',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -15,7 +15,7 @@ import { FormTooltip } from '@/components/ui/tooltip';
|
||||
import { buildSelectOptions } from '@/utils/component-util';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
@ -70,6 +70,18 @@ function BeginForm({ node }: INextOperatorForm) {
|
||||
name: 'enablePrologue',
|
||||
});
|
||||
|
||||
const previousModeRef = useRef(mode);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
previousModeRef.current === AgentDialogueMode.Task &&
|
||||
mode === AgentDialogueMode.Conversational
|
||||
) {
|
||||
form.setValue('enablePrologue', true);
|
||||
}
|
||||
previousModeRef.current = mode;
|
||||
}, [mode, form]);
|
||||
|
||||
const {
|
||||
ok,
|
||||
currentRecord,
|
||||
|
||||
@ -85,7 +85,7 @@ const getUrlWithToken = (token: string, from: string = 'chat') => {
|
||||
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
||||
};
|
||||
|
||||
const useFetchTokenListBeforeOtherStep = () => {
|
||||
export const useFetchTokenListBeforeOtherStep = () => {
|
||||
const { showTokenEmptyError } = useShowTokenEmptyError();
|
||||
const { showBetaEmptyError } = useShowBetaEmptyError();
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { FileUploader } from '@/components/file-uploader';
|
||||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { SwitchFormField } from '@/components/switch-fom-field';
|
||||
import { TavilyFormField } from '@/components/tavily-form-field';
|
||||
import {
|
||||
@ -14,11 +16,26 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { DatasetMetadata } from '../../constants';
|
||||
import { MetadataFilterConditions } from './metadata-filter-conditions';
|
||||
|
||||
export default function ChatBasicSetting() {
|
||||
const { t } = useTranslate('chat');
|
||||
const form = useFormContext();
|
||||
const kbIds: string[] = useWatch({ control: form.control, name: 'kb_ids' });
|
||||
const metadata = useWatch({
|
||||
control: form.control,
|
||||
name: 'meta_data_filter.method',
|
||||
});
|
||||
const hasKnowledge = Array.isArray(kbIds) && kbIds.length > 0;
|
||||
|
||||
const MetadataOptions = Object.values(DatasetMetadata).map((x) => {
|
||||
return {
|
||||
value: x,
|
||||
label: t(`meta.${x}`),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@ -108,6 +125,18 @@ export default function ChatBasicSetting() {
|
||||
></SwitchFormField>
|
||||
<TavilyFormField></TavilyFormField>
|
||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||
{hasKnowledge && (
|
||||
<RAGFlowFormItem
|
||||
label={t('metadata')}
|
||||
name={'meta_data_filter.method'}
|
||||
tooltip={t('metadataTip')}
|
||||
>
|
||||
<SelectWithSearch options={MetadataOptions} />
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
{hasKnowledge && metadata === DatasetMetadata.Manual && (
|
||||
<MetadataFilterConditions kbIds={kbIds}></MetadataFilterConditions>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useParams } from 'umi';
|
||||
import { z } from 'zod';
|
||||
import { DatasetMetadata } from '../../constants';
|
||||
import ChatBasicSetting from './chat-basic-settings';
|
||||
import { ChatModelSettings } from './chat-model-settings';
|
||||
import { ChatPromptEngine } from './chat-prompt-engine';
|
||||
@ -38,6 +39,10 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
|
||||
top_n: 8,
|
||||
vector_similarity_weight: 0.2,
|
||||
top_k: 1024,
|
||||
meta_data_filter: {
|
||||
method: DatasetMetadata.Disabled,
|
||||
manual: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
|
||||
import { SwitchOperatorOptions } from '@/pages/agent/constant';
|
||||
import { useBuildSwitchOperatorOptions } from '@/pages/agent/form/switch-form';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function MetadataFilterConditions({ kbIds }: { kbIds: string[] }) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const name = 'meta_data_filter.manual';
|
||||
const metadata = useFetchKnowledgeMetadata(kbIds);
|
||||
|
||||
const switchOperatorOptions = useBuildSwitchOperatorOptions();
|
||||
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const add = useCallback(
|
||||
(key: string) => () => {
|
||||
append({
|
||||
key,
|
||||
value: '',
|
||||
op: SwitchOperatorOptions[0].value,
|
||||
});
|
||||
},
|
||||
[append],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel>{t('chat.conditions')}</FormLabel>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Button variant={'ghost'} type="button">
|
||||
<Plus />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{Object.keys(metadata.data).map((key, idx) => {
|
||||
return (
|
||||
<DropdownMenuItem key={idx} onClick={add(key)}>
|
||||
{key}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="space-y-5">
|
||||
{fields.map((field, index) => {
|
||||
const typeField = `${name}.${index}.key`;
|
||||
return (
|
||||
<div key={field.id} className="flex w-full items-center gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={typeField}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1 overflow-hidden">
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t('common.pleaseInput')}
|
||||
></Input>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Separator className="w-3 text-text-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`${name}.${index}.op`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1 overflow-hidden">
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
{...field}
|
||||
options={switchOperatorOptions}
|
||||
></SelectWithSearch>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Separator className="w-3 text-text-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`${name}.${index}.value`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1 overflow-hidden">
|
||||
<FormControl>
|
||||
<Input placeholder={t('common.pleaseInput')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||
<X className="text-text-sub-title-invert " />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -43,6 +43,18 @@ export function useChatSettingSchema() {
|
||||
llm_id: z.string().optional(),
|
||||
...vectorSimilarityWeightSchema,
|
||||
...topnSchema,
|
||||
meta_data_filter: z
|
||||
.object({
|
||||
method: z.string().optional(),
|
||||
manual: z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
op: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
return formSchema;
|
||||
|
||||
@ -27,6 +27,7 @@ export function SingleChatBox({ controller }: IProps) {
|
||||
messageContainerRef,
|
||||
sendLoading,
|
||||
derivedMessages,
|
||||
isUploading,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
regenerateMessage,
|
||||
@ -91,6 +92,7 @@ export function SingleChatBox({ controller }: IProps) {
|
||||
}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
onUpload={handleUploadFile}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
7
web/src/pages/next-chats/constants.ts
Normal file
7
web/src/pages/next-chats/constants.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const EmptyConversationId = 'empty';
|
||||
|
||||
export enum DatasetMetadata {
|
||||
Disabled = 'disabled',
|
||||
Automatic = 'automatic',
|
||||
Manual = 'manual',
|
||||
}
|
||||
@ -138,7 +138,8 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
|
||||
const { handleUploadFile, fileIds, clearFileIds } = useUploadFile();
|
||||
const { handleUploadFile, fileIds, clearFileIds, isUploading } =
|
||||
useUploadFile();
|
||||
|
||||
const { send, answer, done } = useSendMessageWithSse(
|
||||
api.completeConversation,
|
||||
@ -285,5 +286,6 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
removeMessageById,
|
||||
stopOutputMessage,
|
||||
handleUploadFile,
|
||||
isUploading,
|
||||
};
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export function useUploadFile() {
|
||||
const { uploadAndParseFile } = useUploadAndParseFile();
|
||||
const { uploadAndParseFile, loading } = useUploadAndParseFile();
|
||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||
|
||||
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
||||
@ -23,5 +23,5 @@ export function useUploadFile() {
|
||||
setFileIds([]);
|
||||
}, []);
|
||||
|
||||
return { handleUploadFile, clearFileIds, fileIds };
|
||||
return { handleUploadFile, clearFileIds, fileIds, isUploading: loading };
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
interface IProps extends IModalProps<any> {
|
||||
documentId: string;
|
||||
chunk: IChunk | IReferenceChunk;
|
||||
chunk: IChunk &
|
||||
IReferenceChunk & { docnm_kwd: string; document_name: string };
|
||||
}
|
||||
function getFileExtensionRegex(filename: string): string {
|
||||
const match = filename.match(/\.([^.]+)$/);
|
||||
@ -30,21 +31,22 @@ const PdfDrawer = ({
|
||||
// const [loaded, setLoaded] = useState(false);
|
||||
const url = getDocumentUrl();
|
||||
|
||||
console.log('chunk--->', chunk.docnm_kwd, url);
|
||||
const [fileType, setFileType] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (chunk.docnm_kwd) {
|
||||
const type = getFileExtensionRegex(chunk.docnm_kwd);
|
||||
if (chunk.docnm_kwd || chunk.document_name) {
|
||||
const type = getFileExtensionRegex(
|
||||
chunk.docnm_kwd || chunk.document_name,
|
||||
);
|
||||
setFileType(type);
|
||||
}
|
||||
}, [chunk.docnm_kwd]);
|
||||
}, [chunk.docnm_kwd, chunk.document_name]);
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
||||
{chunk.docnm_kwd}
|
||||
<FileIcon name={chunk.docnm_kwd || chunk.document_name}></FileIcon>
|
||||
{chunk.docnm_kwd || chunk.document_name}
|
||||
</div>
|
||||
}
|
||||
onCancel={hideModal}
|
||||
|
||||
140
web/src/pages/next-search/embed-app-modal.tsx
Normal file
140
web/src/pages/next-search/embed-app-modal.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import HightLightMarkdown from '@/components/highlight-markdown';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
LanguageAbbreviation,
|
||||
LanguageAbbreviationMap,
|
||||
} from '@/constants/common';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
type IEmbedAppModalProps = {
|
||||
open: any;
|
||||
url: string;
|
||||
token: string;
|
||||
from: string;
|
||||
beta: string;
|
||||
setOpen: (e: any) => void;
|
||||
tenantId: string;
|
||||
};
|
||||
|
||||
const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||
const { t } = useTranslate('chat');
|
||||
const { open, setOpen, token = '', from, beta = '', url, tenantId } = props;
|
||||
const [hideAvatar, setHideAvatar] = useState(false);
|
||||
const [locale, setLocale] = useState('');
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
return Object.values(LanguageAbbreviation).map((x) => ({
|
||||
label: LanguageAbbreviationMap[x],
|
||||
value: x,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const generateIframeSrc = useCallback(() => {
|
||||
// const { visibleAvatar, locale } = values;
|
||||
let src = `${location.origin}${url}?shared_id=${token}&from=${from}&auth=${beta}&tenantId=${tenantId}`;
|
||||
if (hideAvatar) {
|
||||
src += '&visible_avatar=1';
|
||||
}
|
||||
if (locale) {
|
||||
src += `&locale=${locale}`;
|
||||
}
|
||||
return src;
|
||||
}, [beta, from, token, hideAvatar, locale, url, tenantId]);
|
||||
|
||||
// ... existing code ...
|
||||
const text = useMemo(() => {
|
||||
const iframeSrc = generateIframeSrc();
|
||||
return `\`\`\`html
|
||||
<iframe
|
||||
src="${iframeSrc}"
|
||||
style="width: 100%; height: 100%; min-height: 600px"
|
||||
frameborder="0">
|
||||
</iframe>
|
||||
\`\`\``;
|
||||
}, [generateIframeSrc]);
|
||||
// ... existing code ...
|
||||
return (
|
||||
<Modal
|
||||
title={t('embedIntoSite', { keyPrefix: 'common' })}
|
||||
className="!bg-bg-base !text-text-disabled"
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
showfooter={false}
|
||||
footer={null}
|
||||
>
|
||||
<div className="w-full">
|
||||
{/* Hide Avatar Toggle */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">
|
||||
{t('avatarHidden')}
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<Switch
|
||||
checked={hideAvatar}
|
||||
onCheckedChange={(value) => {
|
||||
setHideAvatar(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Locale Select */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">Locale</label>
|
||||
<RAGFlowSelect
|
||||
placeholder="Select a locale"
|
||||
value={locale}
|
||||
onChange={(value) => setLocale(value)}
|
||||
options={languageOptions}
|
||||
></RAGFlowSelect>
|
||||
</div>
|
||||
{/* Embed Code */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">Embed code</label>
|
||||
{/* <div className=" border rounded-lg"> */}
|
||||
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
|
||||
<HightLightMarkdown>{text}</HightLightMarkdown>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
|
||||
{/* ID Field */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium mb-2">ID</label>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={token}
|
||||
readOnly
|
||||
className="flex-1 px-4 py-2 border border-gray-700 rounded-lg bg-bg-base focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigator.clipboard.writeText(token)}
|
||||
className="ml-2 p-2 text-gray-400 hover:text-white transition-colors"
|
||||
title="Copy ID"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h10a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
export default EmbedAppModal;
|
||||
488
web/src/pages/next-search/hooks.ts
Normal file
488
web/src/pages/next-search/hooks.ts
Normal file
@ -0,0 +1,488 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { SharedFrom } from '@/constants/chat';
|
||||
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useSendMessageWithSse,
|
||||
} from '@/hooks/logic-hooks';
|
||||
import { useSetPaginationParams } from '@/hooks/route-hook';
|
||||
import { useKnowledgeBaseId } from '@/hooks/use-knowledge-request';
|
||||
import { ResponsePostType } from '@/interfaces/database/base';
|
||||
import { IAnswer } from '@/interfaces/database/chat';
|
||||
import { ITestingResult } from '@/interfaces/database/knowledge';
|
||||
import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||
import chatService from '@/services/chat-service';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import searchService from '@/services/search-service';
|
||||
import api from '@/utils/api';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { has, isEmpty, trim } from 'lodash';
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useSearchParams } from 'umi';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
import { useShowMindMapDrawer } from '../search/hooks';
|
||||
import { useClickDrawer } from './document-preview-modal/hooks';
|
||||
|
||||
export interface ISearchingProps {
|
||||
searchText?: string;
|
||||
data: ISearchAppDetailProps;
|
||||
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||
setSearchText?: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export type ISearchReturnProps = ReturnType<typeof useSearching>;
|
||||
|
||||
export const useGetSharedSearchParams = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const data_prefix = 'data_';
|
||||
const data = Object.fromEntries(
|
||||
searchParams
|
||||
.entries()
|
||||
.filter(([key]) => key.startsWith(data_prefix))
|
||||
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||
);
|
||||
return {
|
||||
from: searchParams.get('from') as SharedFrom,
|
||||
sharedId: searchParams.get('shared_id'),
|
||||
locale: searchParams.get('locale'),
|
||||
tenantId: searchParams.get('tenantId'),
|
||||
data: data,
|
||||
visibleAvatar: searchParams.get('visible_avatar')
|
||||
? searchParams.get('visible_avatar') !== '1'
|
||||
: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSearchFetchMindMap = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const sharedId = searchParams.get('shared_id');
|
||||
const fetchMindMapFunc = sharedId
|
||||
? searchService.mindmapShare
|
||||
: chatService.getMindMap;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchMindMap'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (params: IAskRequestBody) => {
|
||||
try {
|
||||
const ret = await fetchMindMapFunc(params);
|
||||
return ret?.data?.data ?? {};
|
||||
} catch (error: any) {
|
||||
if (has(error, 'message')) {
|
||||
message.error(error.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchMindMap: mutateAsync };
|
||||
};
|
||||
|
||||
export const useTestChunkRetrieval = (
|
||||
tenantId?: string,
|
||||
): ResponsePostType<ITestingResult> & {
|
||||
testChunk: (...params: any[]) => void;
|
||||
} => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const { page, size: pageSize } = useSetPaginationParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const retrievalTestFunc = shared_id
|
||||
? kbService.retrievalTestShare
|
||||
: kbService.retrieval_test;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testChunk'], // This method is invalid
|
||||
gcTime: 0,
|
||||
mutationFn: async (values: any) => {
|
||||
const { data } = await retrievalTestFunc({
|
||||
...values,
|
||||
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||
page,
|
||||
size: pageSize,
|
||||
tenant_id: tenantId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
...res,
|
||||
documents: res.doc_aggs,
|
||||
};
|
||||
}
|
||||
return (
|
||||
data?.data ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||
loading,
|
||||
testChunk: mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTestChunkAllRetrieval = (
|
||||
tenantId?: string,
|
||||
): ResponsePostType<ITestingResult> & {
|
||||
testChunkAll: (...params: any[]) => void;
|
||||
} => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const { page, size: pageSize } = useSetPaginationParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const retrievalTestFunc = shared_id
|
||||
? kbService.retrievalTestShare
|
||||
: kbService.retrieval_test;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testChunkAll'], // This method is invalid
|
||||
gcTime: 0,
|
||||
mutationFn: async (values: any) => {
|
||||
const { data } = await retrievalTestFunc({
|
||||
...values,
|
||||
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||
doc_ids: [],
|
||||
page,
|
||||
size: pageSize,
|
||||
tenant_id: tenantId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
...res,
|
||||
documents: res.doc_aggs,
|
||||
};
|
||||
}
|
||||
return (
|
||||
data?.data ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||
loading,
|
||||
testChunkAll: mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTestRetrieval = (
|
||||
kbIds: string[],
|
||||
searchStr: string,
|
||||
sendingLoading: boolean,
|
||||
) => {
|
||||
const { testChunk, loading } = useTestChunkRetrieval();
|
||||
const { pagination } = useGetPaginationWithRouter();
|
||||
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||
|
||||
const handleTestChunk = useCallback(() => {
|
||||
const q = trim(searchStr);
|
||||
if (sendingLoading || isEmpty(q)) return;
|
||||
|
||||
testChunk({
|
||||
kb_id: kbIds,
|
||||
highlight: true,
|
||||
question: q,
|
||||
doc_ids: Array.isArray(selectedDocumentIds) ? selectedDocumentIds : [],
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
}, [
|
||||
sendingLoading,
|
||||
searchStr,
|
||||
kbIds,
|
||||
testChunk,
|
||||
selectedDocumentIds,
|
||||
pagination,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
handleTestChunk();
|
||||
}, [handleTestChunk]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
selectedDocumentIds,
|
||||
setSelectedDocumentIds,
|
||||
};
|
||||
};
|
||||
export const useFetchRelatedQuestions = (tenantId?: string) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const retrievalTestFunc = shared_id
|
||||
? searchService.getRelatedQuestionsShare
|
||||
: chatService.getRelatedQuestions;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchRelatedQuestions'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (question: string): Promise<string[]> => {
|
||||
const { data } = await retrievalTestFunc({
|
||||
question,
|
||||
tenant_id: tenantId,
|
||||
});
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchRelatedQuestions: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
|
||||
const { sharedId } = useGetSharedSearchParams();
|
||||
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
||||
sharedId ? api.askShare : api.ask,
|
||||
);
|
||||
|
||||
const { testChunk, loading } = useTestChunkRetrieval(tenantId);
|
||||
const { testChunkAll } = useTestChunkAllRetrieval(tenantId);
|
||||
const [sendingLoading, setSendingLoading] = useState(false);
|
||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||
useFetchRelatedQuestions(tenantId);
|
||||
const [searchStr, setSearchStr] = useState<string>('');
|
||||
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
|
||||
const sendQuestion = useCallback(
|
||||
(question: string) => {
|
||||
const q = trim(question);
|
||||
if (isEmpty(q)) return;
|
||||
setPagination({ page: 1 });
|
||||
setIsFirstRender(false);
|
||||
setCurrentAnswer({} as IAnswer);
|
||||
setSendingLoading(true);
|
||||
send({ kb_ids: kbIds, question: q, tenantId });
|
||||
testChunk({
|
||||
kb_id: kbIds,
|
||||
highlight: true,
|
||||
question: q,
|
||||
page: 1,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
|
||||
fetchRelatedQuestions(q);
|
||||
},
|
||||
[
|
||||
send,
|
||||
testChunk,
|
||||
kbIds,
|
||||
fetchRelatedQuestions,
|
||||
setPagination,
|
||||
pagination.pageSize,
|
||||
tenantId,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSearchStrChange: ChangeEventHandler<HTMLInputElement> =
|
||||
useCallback((e) => {
|
||||
setSearchStr(e.target.value);
|
||||
}, []);
|
||||
|
||||
const handleClickRelatedQuestion = useCallback(
|
||||
(question: string) => () => {
|
||||
if (sendingLoading) return;
|
||||
|
||||
setSearchStr(question);
|
||||
sendQuestion(question);
|
||||
},
|
||||
[sendQuestion, sendingLoading],
|
||||
);
|
||||
|
||||
const handleTestChunk = useCallback(
|
||||
(documentIds: string[], page: number = 1, size: number = 10) => {
|
||||
const q = trim(searchStr);
|
||||
if (sendingLoading || isEmpty(q)) return;
|
||||
|
||||
testChunk({
|
||||
kb_id: kbIds,
|
||||
highlight: true,
|
||||
question: q,
|
||||
doc_ids: documentIds ?? selectedDocumentIds,
|
||||
page,
|
||||
size,
|
||||
});
|
||||
|
||||
testChunkAll({
|
||||
kb_id: kbIds,
|
||||
highlight: true,
|
||||
question: q,
|
||||
doc_ids: [],
|
||||
page,
|
||||
size,
|
||||
});
|
||||
},
|
||||
[
|
||||
searchStr,
|
||||
sendingLoading,
|
||||
testChunk,
|
||||
kbIds,
|
||||
selectedDocumentIds,
|
||||
testChunkAll,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(answer)) {
|
||||
setCurrentAnswer(answer);
|
||||
}
|
||||
}, [answer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (done) {
|
||||
setSendingLoading(false);
|
||||
}
|
||||
}, [done]);
|
||||
|
||||
return {
|
||||
sendQuestion,
|
||||
handleSearchStrChange,
|
||||
handleClickRelatedQuestion,
|
||||
handleTestChunk,
|
||||
setSelectedDocumentIds,
|
||||
loading,
|
||||
sendingLoading,
|
||||
answer: currentAnswer,
|
||||
relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
|
||||
searchStr,
|
||||
setSearchStr,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty: isEmpty(trim(searchStr)),
|
||||
stopOutputMessage,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSearching = ({
|
||||
searchText,
|
||||
data: searchData,
|
||||
setSearchText,
|
||||
}: ISearchingProps) => {
|
||||
const { tenantId } = useGetSharedSearchParams();
|
||||
const {
|
||||
sendQuestion,
|
||||
handleClickRelatedQuestion,
|
||||
handleTestChunk,
|
||||
setSelectedDocumentIds,
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
searchStr,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
setSearchStr,
|
||||
stopOutputMessage,
|
||||
} = useSendQuestion(searchData.search_config.kb_ids, tenantId as string);
|
||||
|
||||
const handleSearchStrChange = useCallback(
|
||||
(value: string) => {
|
||||
console.log('handleSearchStrChange', value);
|
||||
setSearchStr(value);
|
||||
},
|
||||
[setSearchStr],
|
||||
);
|
||||
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
setSearchStr(searchText);
|
||||
sendQuestion(searchText);
|
||||
setSearchText?.('');
|
||||
}
|
||||
}, [searchText, sendQuestion, setSearchStr, setSearchText]);
|
||||
|
||||
const {
|
||||
mindMapVisible,
|
||||
hideMindMapModal,
|
||||
showMindMapModal,
|
||||
mindMapLoading,
|
||||
mindMap,
|
||||
} = useShowMindMapDrawer(searchData.search_config.kb_ids, searchStr);
|
||||
const { chunks, total } = useSelectTestingResult();
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(value: string) => {
|
||||
sendQuestion(value);
|
||||
setSearchStr?.(value);
|
||||
},
|
||||
[setSearchStr, sendQuestion],
|
||||
);
|
||||
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const onChange = (pageNumber: number, pageSize: number) => {
|
||||
setPagination({ page: pageNumber, pageSize });
|
||||
handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
|
||||
};
|
||||
|
||||
return {
|
||||
sendQuestion,
|
||||
handleClickRelatedQuestion,
|
||||
handleSearchStrChange,
|
||||
handleTestChunk,
|
||||
setSelectedDocumentIds,
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
searchStr,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
setSearchStr,
|
||||
stopOutputMessage,
|
||||
|
||||
visible,
|
||||
hideModal,
|
||||
documentId,
|
||||
selectedChunk,
|
||||
clickDocumentButton,
|
||||
mindMapVisible,
|
||||
hideMindMapModal,
|
||||
showMindMapModal,
|
||||
mindMapLoading,
|
||||
mindMap,
|
||||
chunks,
|
||||
total,
|
||||
handleSearch,
|
||||
pagination,
|
||||
onChange,
|
||||
};
|
||||
};
|
||||
@ -8,13 +8,17 @@ import {
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SharedFrom } from '@/constants/chat';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
||||
import { Send, Settings } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
useFetchSearchDetail,
|
||||
} from '../next-searches/hooks';
|
||||
import EmbedAppModal from './embed-app-modal';
|
||||
import './index.less';
|
||||
import SearchHome from './search-home';
|
||||
import { SearchSetting } from './search-setting';
|
||||
@ -24,9 +28,15 @@ export default function SearchPage() {
|
||||
const { navigateToSearchList } = useNavigatePage();
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const { data: SearchData } = useFetchSearchDetail();
|
||||
|
||||
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||
const [openSetting, setOpenSetting] = useState(false);
|
||||
const [openEmbed, setOpenEmbed] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
useEffect(() => {
|
||||
handleOperate();
|
||||
}, [handleOperate]);
|
||||
useEffect(() => {
|
||||
if (isSearching) {
|
||||
setOpenSetting(false);
|
||||
@ -81,8 +91,37 @@ export default function SearchPage() {
|
||||
data={SearchData as ISearchAppDetailProps}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
<EmbedAppModal
|
||||
open={openEmbed}
|
||||
setOpen={setOpenEmbed}
|
||||
url="/next-search/share"
|
||||
token={SearchData?.id as string}
|
||||
from={SharedFrom.Search}
|
||||
beta={beta}
|
||||
tenantId={tenantId}
|
||||
/>
|
||||
}
|
||||
{
|
||||
// <EmbedDialog
|
||||
// visible={openEmbed}
|
||||
// hideModal={setOpenEmbed}
|
||||
// token={SearchData?.id as string}
|
||||
// from={SharedFrom.Search}
|
||||
// beta={beta}
|
||||
// isAgent={false}
|
||||
// ></EmbedDialog>
|
||||
}
|
||||
</div>
|
||||
<div className="absolute right-5 top-12 ">
|
||||
<Button
|
||||
className="bg-text-primary text-bg-base border-b-[#00BEB4] border-b-2"
|
||||
onClick={() => setOpenEmbed(!openEmbed)}
|
||||
>
|
||||
<Send />
|
||||
<div>Embed App</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!isSearching && (
|
||||
<div className="absolute left-5 bottom-12 ">
|
||||
<Button
|
||||
|
||||
@ -13,7 +13,7 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const percent = usePendingMindMap();
|
||||
return (
|
||||
<div className="w-[400px] h-[420px]">
|
||||
<div className="w-full h-full">
|
||||
<div className="flex w-full justify-between items-center mb-2">
|
||||
<div className="text-text-primary font-medium text-base">
|
||||
{t('chunk.mind')}
|
||||
@ -32,11 +32,14 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
||||
</div>
|
||||
)}
|
||||
{!loading && (
|
||||
<div className="bg-bg-card rounded-lg p-4 w-[400px] h-[380px]">
|
||||
<div className="bg-bg-card rounded-lg p-4 w-full h-full">
|
||||
<IndentedTree
|
||||
data={data}
|
||||
show
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
></IndentedTree>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Search } from 'lucide-react';
|
||||
@ -68,42 +67,6 @@ export default function SearchPage({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||
<p className="text-text-primary mb-2 text-xl">Related Search</p>
|
||||
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Related SearchRelated Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Related SearchRelated Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
MultiSelect,
|
||||
MultiSelectOptionType,
|
||||
@ -42,6 +41,7 @@ import {
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
IUpdateSearchProps,
|
||||
IllmSettingProps,
|
||||
useUpdateSearch,
|
||||
} from '../next-searches/hooks';
|
||||
import {
|
||||
@ -55,14 +55,6 @@ interface SearchSettingProps {
|
||||
className?: string;
|
||||
data: ISearchAppDetailProps;
|
||||
}
|
||||
interface ISubmitLlmSettingProps {
|
||||
llm_id: string;
|
||||
parameter: string;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
frequency_penalty?: number;
|
||||
presence_penalty?: number;
|
||||
}
|
||||
|
||||
const SearchSettingFormSchema = z
|
||||
.object({
|
||||
@ -120,16 +112,19 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||
|
||||
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
||||
const resetForm = useCallback(() => {
|
||||
formMethods.reset({
|
||||
search_id: data?.id,
|
||||
name: data?.name || '',
|
||||
avatar: data?.avatar || '',
|
||||
description: data?.description || 'You are an intelligent assistant.',
|
||||
description: data?.description || descriptionDefaultValue,
|
||||
search_config: {
|
||||
kb_ids: search_config?.kb_ids || [],
|
||||
vector_similarity_weight: search_config?.vector_similarity_weight || 20,
|
||||
vector_similarity_weight:
|
||||
(search_config?.vector_similarity_weight
|
||||
? 1 - search_config?.vector_similarity_weight
|
||||
: 0.3) || 0.3,
|
||||
web_search: search_config?.web_search || false,
|
||||
doc_ids: [],
|
||||
similarity_threshold: 0.0,
|
||||
@ -198,8 +193,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
})();
|
||||
}
|
||||
}, [avatarFile]);
|
||||
const { list: datasetListOrigin, loading: datasetLoading } =
|
||||
useFetchKnowledgeList();
|
||||
const { list: datasetListOrigin } = useFetchKnowledgeList();
|
||||
|
||||
useEffect(() => {
|
||||
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
||||
@ -259,7 +253,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
) => {
|
||||
try {
|
||||
const { search_config, ...other_formdata } = formData;
|
||||
const { llm_setting, ...other_config } = search_config;
|
||||
const { llm_setting, vector_similarity_weight, ...other_config } =
|
||||
search_config;
|
||||
const llmSetting = {
|
||||
llm_id: llm_setting.llm_id,
|
||||
parameter: llm_setting.parameter,
|
||||
@ -267,7 +262,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
top_p: llm_setting.top_p,
|
||||
frequency_penalty: llm_setting.frequency_penalty,
|
||||
presence_penalty: llm_setting.presence_penalty,
|
||||
} as ISubmitLlmSettingProps;
|
||||
} as IllmSettingProps;
|
||||
|
||||
if (!llm_setting.frequencyPenaltyEnabled) {
|
||||
delete llmSetting.frequency_penalty;
|
||||
}
|
||||
@ -284,6 +280,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
...other_formdata,
|
||||
search_config: {
|
||||
...other_config,
|
||||
vector_similarity_weight: 1 - vector_similarity_weight,
|
||||
llm_setting: { ...llmSetting },
|
||||
},
|
||||
tenant_id: systemSetting.tenant_id,
|
||||
@ -355,46 +352,54 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
<FormItem>
|
||||
<FormLabel>Avatar</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative group">
|
||||
{!avatarBase64Str ? (
|
||||
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
||||
<div className="flex flex-col items-center">
|
||||
<Upload />
|
||||
<p>{t('common.upload')}</p>
|
||||
<div className="relative group flex items-end gap-2">
|
||||
<div>
|
||||
{!avatarBase64Str ? (
|
||||
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
||||
<div className="flex flex-col items-center">
|
||||
<Upload />
|
||||
<p>{t('common.upload')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
||||
<RAGFlowAvatar
|
||||
avatar={avatarBase64Str}
|
||||
name={data.name}
|
||||
className="w-[64px] h-[64px] rounded-md block"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#000]/20 group-hover:bg-[#000]/60">
|
||||
<Pencil
|
||||
size={20}
|
||||
className="absolute right-2 bottom-0 opacity-50 hidden group-hover:block"
|
||||
) : (
|
||||
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
||||
<RAGFlowAvatar
|
||||
avatar={avatarBase64Str}
|
||||
name={data.name}
|
||||
className="w-[64px] h-[64px] rounded-md block"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#000]/20 group-hover:bg-[#000]/60">
|
||||
<Pencil
|
||||
size={20}
|
||||
className="absolute right-2 bottom-0 opacity-50 hidden group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
placeholder=""
|
||||
// {...field}
|
||||
type="file"
|
||||
title=""
|
||||
accept="image/*"
|
||||
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
||||
onChange={(ev) => {
|
||||
const file = ev.target?.files?.[0];
|
||||
if (
|
||||
/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')
|
||||
) {
|
||||
setAvatarFile(file!);
|
||||
}
|
||||
ev.target.value = '';
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
placeholder=""
|
||||
// {...field}
|
||||
type="file"
|
||||
title=""
|
||||
accept="image/*"
|
||||
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
||||
onChange={(ev) => {
|
||||
const file = ev.target?.files?.[0];
|
||||
if (
|
||||
/\.(jpg|jpeg|png|webp|bmp)$/i.test(
|
||||
file?.name ?? '',
|
||||
)
|
||||
) {
|
||||
setAvatarFile(file!);
|
||||
}
|
||||
ev.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="margin-1 text-muted-foreground">
|
||||
{t('knowledgeConfiguration.photoTip')}
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@ -410,7 +415,20 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Description" {...field} />
|
||||
<Input
|
||||
placeholder="You are an intelligent assistant."
|
||||
{...field}
|
||||
onFocus={() => {
|
||||
if (field.value === descriptionDefaultValue) {
|
||||
field.onChange('');
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (field.value === '') {
|
||||
field.onChange(descriptionDefaultValue);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -451,26 +469,58 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
control={formMethods.control}
|
||||
name="search_config.vector_similarity_weight"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Keyword
|
||||
Similarity Weight
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex justify-between items-center">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-4 justify-between',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<FormControl>
|
||||
<SingleFormSlider
|
||||
max={100}
|
||||
step={1}
|
||||
value={field.value as number}
|
||||
onChange={(values) => field.onChange(values)}
|
||||
{...field}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
></SingleFormSlider>
|
||||
<Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
||||
{field.value}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'number'}
|
||||
className="h-7 w-20 bg-bg-card"
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
{...field}
|
||||
></Input>
|
||||
</FormControl>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
// <FormItem className="flex flex-col">
|
||||
// <FormLabel>
|
||||
// <span className="text-destructive mr-1"> *</span>Keyword
|
||||
// Similarity Weight
|
||||
// </FormLabel>
|
||||
// <FormControl>
|
||||
// {/* <div className="flex justify-between items-center">
|
||||
// <SingleFormSlider
|
||||
// max={100}
|
||||
// step={1}
|
||||
// value={field.value as number}
|
||||
// onChange={(values) => field.onChange(values)}
|
||||
// ></SingleFormSlider>
|
||||
// <Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
||||
// {field.value}
|
||||
// </Label>
|
||||
// </div> */}
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -528,16 +578,15 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
<FormControl>
|
||||
<SingleFormSlider
|
||||
{...field}
|
||||
max={100}
|
||||
max={2048}
|
||||
min={0}
|
||||
step={1}
|
||||
></SingleFormSlider>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'number'}
|
||||
className="h-7 w-20 bg-bg-card"
|
||||
max={100}
|
||||
max={2048}
|
||||
min={0}
|
||||
step={1}
|
||||
{...field}
|
||||
|
||||
321
web/src/pages/next-search/search-view.tsx
Normal file
321
web/src/pages/next-search/search-view.tsx
Normal file
@ -0,0 +1,321 @@
|
||||
import { FileIcon } from '@/components/icon-font';
|
||||
import { ImageWithPopover } from '@/components/image';
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { IReference } from '@/interfaces/database/chat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { TFunction } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { BrainCircuit, Search, Square, X } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
import PdfDrawer from './document-preview-modal';
|
||||
import HightLightMarkdown from './highlight-markdown';
|
||||
import { ISearchReturnProps } from './hooks';
|
||||
import './index.less';
|
||||
import MarkdownContent from './markdown-content';
|
||||
import MindMapDrawer from './mindmap-drawer';
|
||||
import RetrievalDocuments from './retrieval-documents';
|
||||
export default function SearchingView({
|
||||
setIsSearching,
|
||||
searchData,
|
||||
handleClickRelatedQuestion,
|
||||
handleTestChunk,
|
||||
setSelectedDocumentIds,
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
searchStr,
|
||||
stopOutputMessage,
|
||||
visible,
|
||||
hideModal,
|
||||
documentId,
|
||||
selectedChunk,
|
||||
clickDocumentButton,
|
||||
mindMapVisible,
|
||||
hideMindMapModal,
|
||||
showMindMapModal,
|
||||
mindMapLoading,
|
||||
mindMap,
|
||||
chunks,
|
||||
total,
|
||||
handleSearch,
|
||||
pagination,
|
||||
onChange,
|
||||
t,
|
||||
}: ISearchReturnProps & {
|
||||
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||
searchData: ISearchAppDetailProps;
|
||||
t: TFunction<'translation', undefined>;
|
||||
}) {
|
||||
const { t: tt, i18n } = useTranslation();
|
||||
useEffect(() => {
|
||||
const changeLanguage = async () => {
|
||||
await i18n.changeLanguage('zh');
|
||||
};
|
||||
changeLanguage();
|
||||
}, [i18n]);
|
||||
const [searchtext, setSearchtext] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setSearchtext(searchStr);
|
||||
}, [searchStr, setSearchtext]);
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'relative w-full flex transition-all justify-start items-center',
|
||||
)}
|
||||
>
|
||||
{/* search header */}
|
||||
<div
|
||||
className={cn(
|
||||
'relative z-10 px-8 pt-8 flex text-transparent justify-start items-start w-full',
|
||||
)}
|
||||
>
|
||||
<h1
|
||||
className={cn(
|
||||
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text cursor-pointer',
|
||||
)}
|
||||
onClick={() => {
|
||||
setIsSearching?.(false);
|
||||
}}
|
||||
>
|
||||
RAGFlow
|
||||
</h1>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
' rounded-lg text-primary text-xl sticky flex flex-col justify-center w-2/3 max-w-[780px] transform scale-100 ml-16 ',
|
||||
)}
|
||||
>
|
||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||
<div className="relative w-full text-primary">
|
||||
<Input
|
||||
placeholder={tt('search.searchGreeting')}
|
||||
className={cn(
|
||||
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
||||
)}
|
||||
value={searchtext}
|
||||
onChange={(e) => {
|
||||
setSearchtext(e.target.value);
|
||||
}}
|
||||
disabled={sendingLoading}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearch(searchtext);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
||||
<X
|
||||
className="text-text-secondary"
|
||||
size={14}
|
||||
onClick={() => {
|
||||
handleClickRelatedQuestion('');
|
||||
}}
|
||||
/>
|
||||
<span className="text-text-secondary">|</span>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
||||
onClick={() => {
|
||||
if (sendingLoading) {
|
||||
stopOutputMessage();
|
||||
} else {
|
||||
handleSearch(searchtext);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{sendingLoading ? (
|
||||
<Square size={22} className="m-auto" />
|
||||
) : (
|
||||
<Search size={22} className="m-auto" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* search body */}
|
||||
<div
|
||||
className="w-full mt-5 overflow-auto scrollbar-none "
|
||||
style={{ height: 'calc(100vh - 250px)' }}
|
||||
>
|
||||
{searchData.search_config.summary && !isSearchStrEmpty && (
|
||||
<>
|
||||
<div className="flex justify-start items-start text-text-primary text-2xl">
|
||||
AI Summary
|
||||
</div>
|
||||
{isEmpty(answer) && sendingLoading ? (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
||||
</div>
|
||||
) : (
|
||||
answer.answer && (
|
||||
<div className="border rounded-lg p-4 mt-3 max-h-52 overflow-auto scrollbar-none">
|
||||
<MarkdownContent
|
||||
loading={sendingLoading}
|
||||
content={answer.answer}
|
||||
reference={answer.reference ?? ({} as IReference)}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||
</>
|
||||
)}
|
||||
{/* retrieval documents */}
|
||||
{!isSearchStrEmpty && (
|
||||
<>
|
||||
<div className=" mt-3 w-44 ">
|
||||
<RetrievalDocuments
|
||||
selectedDocumentIds={selectedDocumentIds}
|
||||
setSelectedDocumentIds={setSelectedDocumentIds}
|
||||
onTesting={handleTestChunk}
|
||||
></RetrievalDocuments>
|
||||
</div>
|
||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-3 ">
|
||||
<Spin spinning={loading}>
|
||||
{chunks?.length > 0 && (
|
||||
<>
|
||||
{chunks.map((chunk, index) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={chunk.chunk_id}
|
||||
className="w-full flex flex-col"
|
||||
>
|
||||
<div className="w-full">
|
||||
<ImageWithPopover
|
||||
id={chunk.img_id}
|
||||
></ImageWithPopover>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
`${chunk.highlight}...`,
|
||||
),
|
||||
}}
|
||||
className="text-sm text-text-primary mb-1"
|
||||
></div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="text-text-primary">
|
||||
<HightLightMarkdown>
|
||||
{chunk.content_with_weight}
|
||||
</HightLightMarkdown>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div
|
||||
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
||||
onClick={() =>
|
||||
clickDocumentButton(chunk.doc_id, chunk as any)
|
||||
}
|
||||
>
|
||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
||||
{chunk.docnm_kwd}
|
||||
</div>
|
||||
</div>
|
||||
{index < chunks.length - 1 && (
|
||||
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
{relatedQuestions?.length > 0 && (
|
||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||
<p className="text-text-primary mb-2 text-xl">
|
||||
Related Search
|
||||
</p>
|
||||
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
||||
{relatedQuestions?.map((x, idx) => (
|
||||
<Button
|
||||
key={idx}
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
onClick={handleClickRelatedQuestion(x)}
|
||||
>
|
||||
Related Search{x}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{total > 0 && (
|
||||
<div className="mt-8 px-8 pb-8">
|
||||
<RAGFlowPagination
|
||||
current={pagination.current}
|
||||
pageSize={pagination.pageSize}
|
||||
total={total}
|
||||
onChange={onChange}
|
||||
></RAGFlowPagination>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{mindMapVisible && (
|
||||
<div className="flex-1 h-[88dvh] z-30 ml-8 mt-5">
|
||||
<MindMapDrawer
|
||||
visible={mindMapVisible}
|
||||
hideModal={hideMindMapModal}
|
||||
data={mindMap}
|
||||
loading={mindMapLoading}
|
||||
></MindMapDrawer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!mindMapVisible &&
|
||||
!isFirstRender &&
|
||||
!isSearchStrEmpty &&
|
||||
!isEmpty(searchData.search_config.kb_ids) &&
|
||||
searchData.search_config.query_mindmap && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className="rounded-lg h-16 w-16 p-0 absolute top-28 right-3 z-30 border cursor-pointer flex justify-center items-center bg-bg-card"
|
||||
onClick={showMindMapModal}
|
||||
>
|
||||
{/* <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon> */}
|
||||
<BrainCircuit size={36} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit">{t('chunk.mind')}</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
{visible && (
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
></PdfDrawer>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -1,332 +1,33 @@
|
||||
import { FileIcon } from '@/components/icon-font';
|
||||
import { ImageWithPopover } from '@/components/image';
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
||||
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
|
||||
import { IReference } from '@/interfaces/database/chat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { BrainCircuit, Search, Square, Tag, X } from 'lucide-react';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
import { useSendQuestion, useShowMindMapDrawer } from '../search/hooks';
|
||||
import PdfDrawer from './document-preview-modal';
|
||||
import HightLightMarkdown from './highlight-markdown';
|
||||
import { useSearching } from './hooks';
|
||||
import './index.less';
|
||||
import styles from './index.less';
|
||||
import MarkdownContent from './markdown-content';
|
||||
import MindMapDrawer from './mindmap-drawer';
|
||||
import RetrievalDocuments from './retrieval-documents';
|
||||
import SearchingView from './search-view';
|
||||
export default function SearchingPage({
|
||||
searchText,
|
||||
data: searchData,
|
||||
setIsSearching,
|
||||
setSearchText,
|
||||
}: {
|
||||
searchText: string;
|
||||
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
||||
setSearchText: Dispatch<SetStateAction<string>>;
|
||||
data: ISearchAppDetailProps;
|
||||
}) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const {
|
||||
sendQuestion,
|
||||
handleClickRelatedQuestion,
|
||||
handleSearchStrChange,
|
||||
handleTestChunk,
|
||||
setSelectedDocumentIds,
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
searchStr,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
setSearchStr,
|
||||
stopOutputMessage,
|
||||
} = useSendQuestion(searchData.search_config.kb_ids);
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
setSearchStr(searchText);
|
||||
sendQuestion(searchText);
|
||||
}
|
||||
// regain focus
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [searchText, sendQuestion, setSearchStr]);
|
||||
|
||||
const {
|
||||
mindMapVisible,
|
||||
hideMindMapModal,
|
||||
showMindMapModal,
|
||||
mindMapLoading,
|
||||
mindMap,
|
||||
} = useShowMindMapDrawer(searchData.search_config.kb_ids, searchStr);
|
||||
const { chunks, total } = useSelectTestingResult();
|
||||
const handleSearch = useCallback(() => {
|
||||
sendQuestion(searchStr);
|
||||
}, [searchStr, sendQuestion]);
|
||||
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const onChange = (pageNumber: number, pageSize: number) => {
|
||||
setPagination({ page: pageNumber, pageSize });
|
||||
handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
|
||||
};
|
||||
|
||||
const searchingParam = useSearching({
|
||||
searchText,
|
||||
data: searchData,
|
||||
setIsSearching,
|
||||
setSearchText,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'relative w-full flex transition-all justify-start items-center',
|
||||
)}
|
||||
>
|
||||
{/* search header */}
|
||||
<div
|
||||
className={cn(
|
||||
'relative z-10 px-8 pt-8 flex text-transparent justify-start items-start w-full',
|
||||
)}
|
||||
>
|
||||
<h1
|
||||
className={cn(
|
||||
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text cursor-pointer',
|
||||
)}
|
||||
onClick={() => {
|
||||
setIsSearching(false);
|
||||
}}
|
||||
>
|
||||
RAGFlow
|
||||
</h1>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
' rounded-lg text-primary text-xl sticky flex flex-col justify-center w-2/3 max-w-[780px] transform scale-100 ml-16 ',
|
||||
)}
|
||||
>
|
||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||
<div className="relative w-full text-primary">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
key="search-input"
|
||||
placeholder="How can I help you today?"
|
||||
className={cn(
|
||||
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
||||
)}
|
||||
value={searchStr}
|
||||
onChange={handleSearchStrChange}
|
||||
disabled={sendingLoading}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearch();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
||||
<X
|
||||
className="text-text-secondary"
|
||||
size={14}
|
||||
onClick={() => {
|
||||
handleClickRelatedQuestion('');
|
||||
}}
|
||||
/>
|
||||
<span className="text-text-secondary">|</span>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
||||
onClick={() => {
|
||||
if (sendingLoading) {
|
||||
stopOutputMessage();
|
||||
} else {
|
||||
handleSearch();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{sendingLoading ? (
|
||||
<Square size={22} className="m-auto" />
|
||||
) : (
|
||||
<Search size={22} className="m-auto" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* search body */}
|
||||
<div
|
||||
className="w-full mt-5 overflow-auto scrollbar-none "
|
||||
style={{ height: 'calc(100vh - 250px)' }}
|
||||
>
|
||||
{searchData.search_config.summary && (
|
||||
<>
|
||||
<div className="flex justify-start items-start text-text-primary text-2xl">
|
||||
AI Summary
|
||||
</div>
|
||||
{isEmpty(answer) && sendingLoading ? (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
||||
</div>
|
||||
) : (
|
||||
answer.answer && (
|
||||
<div className="border rounded-lg p-4 mt-3 max-h-52 overflow-auto scrollbar-none">
|
||||
<MarkdownContent
|
||||
loading={sendingLoading}
|
||||
content={answer.answer}
|
||||
reference={answer.reference ?? ({} as IReference)}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||
{/* retrieval documents */}
|
||||
<div className=" mt-3 w-44 ">
|
||||
<RetrievalDocuments
|
||||
selectedDocumentIds={selectedDocumentIds}
|
||||
setSelectedDocumentIds={setSelectedDocumentIds}
|
||||
onTesting={handleTestChunk}
|
||||
></RetrievalDocuments>
|
||||
</div>
|
||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||
<div className="mt-3 ">
|
||||
<Spin spinning={loading}>
|
||||
{chunks?.length > 0 && (
|
||||
<>
|
||||
{chunks.map((chunk, index) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={chunk.chunk_id}
|
||||
className="w-full flex flex-col"
|
||||
>
|
||||
<div className="w-full">
|
||||
<ImageWithPopover
|
||||
id={chunk.img_id}
|
||||
></ImageWithPopover>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
`${chunk.highlight}...`,
|
||||
),
|
||||
}}
|
||||
className="text-sm text-text-primary mb-1"
|
||||
></div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="text-text-primary">
|
||||
<HightLightMarkdown>
|
||||
{chunk.content_with_weight}
|
||||
</HightLightMarkdown>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div
|
||||
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
||||
onClick={() =>
|
||||
clickDocumentButton(chunk.doc_id, chunk as any)
|
||||
}
|
||||
>
|
||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
||||
{chunk.docnm_kwd}
|
||||
</div>
|
||||
</div>
|
||||
{index < chunks.length - 1 && (
|
||||
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
{relatedQuestions?.length > 0 && (
|
||||
<div title={t('chat.relatedQuestion')}>
|
||||
<div className="flex gap-2">
|
||||
{relatedQuestions?.map((x, idx) => (
|
||||
<Tag
|
||||
key={idx}
|
||||
className={styles.tag}
|
||||
onClick={handleClickRelatedQuestion(x)}
|
||||
>
|
||||
{x}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 px-8 pb-8">
|
||||
<RAGFlowPagination
|
||||
current={pagination.current}
|
||||
pageSize={pagination.pageSize}
|
||||
total={total}
|
||||
onChange={onChange}
|
||||
></RAGFlowPagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!mindMapVisible &&
|
||||
!isFirstRender &&
|
||||
!isSearchStrEmpty &&
|
||||
!isEmpty(searchData.search_config.kb_ids) && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className="rounded-lg h-8 w-8 p-0 absolute top-28 right-3 z-30"
|
||||
variant={'transparent'}
|
||||
onClick={showMindMapModal}
|
||||
>
|
||||
{/* <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon> */}
|
||||
<BrainCircuit size={24} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit">{t('chunk.mind')}</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
{visible && (
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
></PdfDrawer>
|
||||
)}
|
||||
{mindMapVisible && (
|
||||
<div className="absolute top-20 right-16 z-30">
|
||||
<MindMapDrawer
|
||||
visible={mindMapVisible}
|
||||
hideModal={hideMindMapModal}
|
||||
data={mindMap}
|
||||
loading={mindMapLoading}
|
||||
></MindMapDrawer>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<SearchingView
|
||||
{...searchingParam}
|
||||
searchData={searchData}
|
||||
setIsSearching={setIsSearching}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
35
web/src/pages/next-search/share/index.tsx
Normal file
35
web/src/pages/next-search/share/index.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import i18n from '@/locales/config';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
useFetchSearchDetail,
|
||||
} from '../../next-searches/hooks';
|
||||
import { useGetSharedSearchParams, useSearching } from '../hooks';
|
||||
import '../index.less';
|
||||
import SearchingView from '../search-view';
|
||||
export default function SearchingPage() {
|
||||
const { tenantId, locale } = useGetSharedSearchParams();
|
||||
const {
|
||||
data: searchData = {
|
||||
search_config: { kb_ids: [] },
|
||||
} as unknown as ISearchAppDetailProps,
|
||||
} = useFetchSearchDetail(tenantId as string);
|
||||
const searchingParam = useSearching({
|
||||
data: searchData,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (locale) {
|
||||
// i18n.changeLanguage(locale);
|
||||
// }
|
||||
// }, [locale, i18n]);
|
||||
useEffect(() => {
|
||||
console.log('locale', locale, i18n.language);
|
||||
if (locale && i18n.language !== locale) {
|
||||
i18n.changeLanguage(locale);
|
||||
}
|
||||
}, [locale]);
|
||||
return <SearchingView {...searchingParam} searchData={searchData} t={t} />;
|
||||
}
|
||||
@ -5,7 +5,7 @@ import searchService from '@/services/search-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
|
||||
interface CreateSearchProps {
|
||||
name: string;
|
||||
@ -156,13 +156,13 @@ export const useDeleteSearch = () => {
|
||||
return { data, isError, deleteSearch };
|
||||
};
|
||||
|
||||
interface IllmSettingProps {
|
||||
export interface IllmSettingProps {
|
||||
llm_id: string;
|
||||
parameter: string;
|
||||
temperature: number;
|
||||
top_p: number;
|
||||
frequency_penalty: number;
|
||||
presence_penalty: number;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
frequency_penalty?: number;
|
||||
presence_penalty?: number;
|
||||
}
|
||||
interface IllmSettingEnableProps {
|
||||
temperatureEnabled?: boolean;
|
||||
@ -204,14 +204,29 @@ interface SearchDetailResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const useFetchSearchDetail = () => {
|
||||
export const useFetchSearchDetail = (tenantId?: string) => {
|
||||
const { id } = useParams();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const searchId = id || shared_id;
|
||||
let param: { search_id: string | null; tenant_id?: string } = {
|
||||
search_id: searchId,
|
||||
};
|
||||
if (shared_id) {
|
||||
param = {
|
||||
search_id: searchId,
|
||||
tenant_id: tenantId,
|
||||
};
|
||||
}
|
||||
const fetchSearchDetailFunc = shared_id
|
||||
? searchService.getSearchDetailShare
|
||||
: searchService.getSearchDetail;
|
||||
const { data, isLoading, isError } = useQuery<SearchDetailResponse, Error>({
|
||||
queryKey: ['searchDetail', id],
|
||||
queryKey: ['searchDetail', searchId],
|
||||
enabled: !shared_id || !!tenantId,
|
||||
queryFn: async () => {
|
||||
const { data: response } = await searchService.getSearchDetail({
|
||||
search_id: id,
|
||||
});
|
||||
const { data: response } = await fetchSearchDetailFunc(param);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to fetch search detail');
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ export default function SearchList() {
|
||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
||||
};
|
||||
return (
|
||||
<section>
|
||||
<section className="w-full h-full flex flex-col">
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar
|
||||
icon={
|
||||
@ -89,18 +89,23 @@ export default function SearchList() {
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</div>
|
||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
||||
{list?.data.search_apps.map((x) => {
|
||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||
})}
|
||||
<div className="flex-1">
|
||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
||||
{list?.data.search_apps.map((x) => {
|
||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{list?.data.total && (
|
||||
<RAGFlowPagination
|
||||
{...pick(searchParams, 'current', 'pageSize')}
|
||||
total={list?.data.total}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
{list?.data.total && list?.data.total > 0 && (
|
||||
<div className="px-8 mb-4">
|
||||
<RAGFlowPagination
|
||||
{...pick(searchParams, 'current', 'pageSize')}
|
||||
total={list?.data.total}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
open={openCreateModal}
|
||||
onOpenChange={(open) => {
|
||||
|
||||
@ -19,7 +19,7 @@ export function SearchCard({ data }: IProps) {
|
||||
navigateToSearch(data?.id);
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-4 flex gap-2 items-start group">
|
||||
<CardContent className="p-4 flex gap-2 items-start group h-full">
|
||||
<div className="flex justify-between mb-4">
|
||||
<RAGFlowAvatar
|
||||
className="w-[32px] h-[32px]"
|
||||
@ -27,7 +27,7 @@ export function SearchCard({ data }: IProps) {
|
||||
name={data.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 flex-1">
|
||||
<div className="flex flex-col justify-between gap-1 flex-1 h-full">
|
||||
<section className="flex justify-between">
|
||||
<div className="text-[20px] font-bold w-80% leading-5">
|
||||
{data.name}
|
||||
@ -37,22 +37,13 @@ export function SearchCard({ data }: IProps) {
|
||||
</SearchDropdown>
|
||||
</section>
|
||||
|
||||
<div>{data.description}</div>
|
||||
<section className="flex justify-between">
|
||||
<section className="flex flex-col gap-1 mt-1">
|
||||
<div>{data.description}</div>
|
||||
<div>
|
||||
Search app
|
||||
<p className="text-sm opacity-80">
|
||||
{formatDate(data.update_time)}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="space-x-2 invisible group-hover:visible">
|
||||
<Button variant="icon" size="icon" onClick={navigateToSearch}>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="icon" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div> */}
|
||||
</section>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
|
||||
import { useFetchRelatedQuestions } from '@/hooks/chat-hooks';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useTestChunkAllRetrieval,
|
||||
@ -18,17 +18,23 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
useGetSharedSearchParams,
|
||||
useSearchFetchMindMap,
|
||||
} from '../next-search/hooks';
|
||||
|
||||
export const useSendQuestion = (kbIds: string[]) => {
|
||||
export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
|
||||
const { sharedId } = useGetSharedSearchParams();
|
||||
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
||||
api.ask,
|
||||
sharedId ? api.askShare : api.ask,
|
||||
);
|
||||
const { testChunk, loading } = useTestChunkRetrieval();
|
||||
const { testChunkAll } = useTestChunkAllRetrieval();
|
||||
|
||||
const { testChunk, loading } = useTestChunkRetrieval(tenantId);
|
||||
const { testChunkAll } = useTestChunkAllRetrieval(tenantId);
|
||||
const [sendingLoading, setSendingLoading] = useState(false);
|
||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||
useFetchRelatedQuestions();
|
||||
useFetchRelatedQuestions(tenantId);
|
||||
const [searchStr, setSearchStr] = useState<string>('');
|
||||
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||
@ -43,7 +49,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
setIsFirstRender(false);
|
||||
setCurrentAnswer({} as IAnswer);
|
||||
setSendingLoading(true);
|
||||
send({ kb_ids: kbIds, question: q });
|
||||
send({ kb_ids: kbIds, question: q, tenantId });
|
||||
testChunk({
|
||||
kb_id: kbIds,
|
||||
highlight: true,
|
||||
@ -61,6 +67,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
fetchRelatedQuestions,
|
||||
setPagination,
|
||||
pagination.pageSize,
|
||||
tenantId,
|
||||
],
|
||||
);
|
||||
|
||||
@ -218,7 +225,7 @@ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
|
||||
fetchMindMap,
|
||||
data: mindMap,
|
||||
loading: mindMapLoading,
|
||||
} = useFetchMindMap();
|
||||
} = useSearchFetchMindMap();
|
||||
|
||||
const handleShowModal = useCallback(() => {
|
||||
const searchParams = { question: trim(question), kb_ids: kbIds };
|
||||
|
||||
@ -11,6 +11,7 @@ export enum Routes {
|
||||
AgentList = '/agent-list',
|
||||
Searches = '/next-searches',
|
||||
Search = '/next-search',
|
||||
SearchShare = '/next-search/share',
|
||||
Chats = '/next-chats',
|
||||
Chat = '/next-chat',
|
||||
Files = '/files',
|
||||
@ -234,6 +235,11 @@ const routes = [
|
||||
layout: false,
|
||||
component: `@/pages${Routes.Search}`,
|
||||
},
|
||||
{
|
||||
path: `${Routes.SearchShare}`,
|
||||
layout: false,
|
||||
component: `@/pages${Routes.SearchShare}`,
|
||||
},
|
||||
{
|
||||
path: Routes.Agents,
|
||||
layout: false,
|
||||
|
||||
@ -38,6 +38,7 @@ const {
|
||||
listTagByKnowledgeIds,
|
||||
setMeta,
|
||||
getMeta,
|
||||
retrievalTestShare,
|
||||
} = api;
|
||||
|
||||
const methods = {
|
||||
@ -164,6 +165,10 @@ const methods = {
|
||||
url: getMeta,
|
||||
method: 'get',
|
||||
},
|
||||
retrievalTestShare: {
|
||||
url: retrievalTestShare,
|
||||
method: 'post',
|
||||
},
|
||||
};
|
||||
|
||||
const kbService = registerServer<keyof typeof methods>(methods, request);
|
||||
|
||||
@ -8,6 +8,10 @@ const {
|
||||
deleteSearch,
|
||||
getSearchDetail,
|
||||
updateSearchSetting,
|
||||
askShare,
|
||||
mindmapShare,
|
||||
getRelatedQuestionsShare,
|
||||
getSearchDetailShare,
|
||||
} = api;
|
||||
const methods = {
|
||||
createSearch: {
|
||||
@ -27,6 +31,23 @@ const methods = {
|
||||
url: updateSearchSetting,
|
||||
method: 'post',
|
||||
},
|
||||
askShare: {
|
||||
url: askShare,
|
||||
method: 'post',
|
||||
},
|
||||
mindmapShare: {
|
||||
url: mindmapShare,
|
||||
method: 'post',
|
||||
},
|
||||
getRelatedQuestionsShare: {
|
||||
url: getRelatedQuestionsShare,
|
||||
method: 'post',
|
||||
},
|
||||
|
||||
getSearchDetailShare: {
|
||||
url: getSearchDetailShare,
|
||||
method: 'get',
|
||||
},
|
||||
} as const;
|
||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||
|
||||
|
||||
@ -181,5 +181,10 @@ export default {
|
||||
getSearchList: `${api_host}/search/list`,
|
||||
deleteSearch: `${api_host}/search/rm`,
|
||||
getSearchDetail: `${api_host}/search/detail`,
|
||||
getSearchDetailShare: `${ExternalApi}${api_host}/searchbots/detail`,
|
||||
updateSearchSetting: `${api_host}/search/update`,
|
||||
askShare: `${ExternalApi}${api_host}/searchbots/ask`,
|
||||
mindmapShare: `${ExternalApi}${api_host}/searchbots/mindmap`,
|
||||
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
|
||||
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user