mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-06 02:25:05 +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
|
@staticmethod
|
||||||
def string_format(content: str, kv: dict[str, str]) -> str:
|
def string_format(content: str, kv: dict[str, str]) -> str:
|
||||||
for n, v in kv.items():
|
for n, v in kv.items():
|
||||||
|
def repl(_match, val=v):
|
||||||
|
return str(val) if val is not None else ""
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
r"\{%s\}" % re.escape(n), v, content
|
r"\{%s\}" % re.escape(n),
|
||||||
|
repl,
|
||||||
|
content
|
||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from api.db.db_models import close_connection
|
|||||||
from api.db.services import UserService
|
from api.db.services import UserService
|
||||||
from api.utils import CustomJSONEncoder, commands
|
from api.utils import CustomJSONEncoder, commands
|
||||||
|
|
||||||
|
from flask_mail import Mail
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from api import settings
|
from api import settings
|
||||||
@ -40,6 +41,7 @@ __all__ = ["app"]
|
|||||||
Request.json = property(lambda self: self.get_json(force=True, silent=True))
|
Request.json = property(lambda self: self.get_json(force=True, silent=True))
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
smtp_mail_server = Mail()
|
||||||
|
|
||||||
# Add this at the beginning of your file to configure Swagger UI
|
# Add this at the beginning of your file to configure Swagger UI
|
||||||
swagger_config = {
|
swagger_config = {
|
||||||
@ -146,16 +148,16 @@ def load_user(web_request):
|
|||||||
if authorization:
|
if authorization:
|
||||||
try:
|
try:
|
||||||
access_token = str(jwt.loads(authorization))
|
access_token = str(jwt.loads(authorization))
|
||||||
|
|
||||||
if not access_token or not access_token.strip():
|
if not access_token or not access_token.strip():
|
||||||
logging.warning("Authentication attempt with empty access token")
|
logging.warning("Authentication attempt with empty access token")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Access tokens should be UUIDs (32 hex characters)
|
# Access tokens should be UUIDs (32 hex characters)
|
||||||
if len(access_token.strip()) < 32:
|
if len(access_token.strip()) < 32:
|
||||||
logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars")
|
logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
user = UserService.query(
|
user = UserService.query(
|
||||||
access_token=access_token, status=StatusEnum.VALID.value
|
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.dialog_service import DialogService, ask, chat
|
||||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||||
from api.db.services.llm_service import LLMBundle
|
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 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 graphrag.general.mind_map_extractor import MindMapExtractor
|
||||||
from rag.app.tag import label_question
|
from rag.app.tag import label_question
|
||||||
@ -66,8 +67,14 @@ def set_conversation():
|
|||||||
e, dia = DialogService.get_by_id(req["dialog_id"])
|
e, dia = DialogService.get_by_id(req["dialog_id"])
|
||||||
if not e:
|
if not e:
|
||||||
return get_data_error_result(message="Dialog not found")
|
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,
|
conv = {
|
||||||
"reference":[],}
|
"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)
|
ConversationService.save(**conv)
|
||||||
return get_json_result(data=conv)
|
return get_json_result(data=conv)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -174,6 +181,21 @@ def completion():
|
|||||||
continue
|
continue
|
||||||
msg.append(m)
|
msg.append(m)
|
||||||
message_id = msg[-1].get("id")
|
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:
|
try:
|
||||||
e, conv = ConversationService.get_by_id(req["conversation_id"])
|
e, conv = ConversationService.get_by_id(req["conversation_id"])
|
||||||
if not e:
|
if not e:
|
||||||
@ -190,13 +212,23 @@ def completion():
|
|||||||
conv.reference = [r for r in conv.reference if r]
|
conv.reference = [r for r in conv.reference if r]
|
||||||
conv.reference.append({"chunks": [], "doc_aggs": []})
|
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():
|
def stream():
|
||||||
nonlocal dia, msg, req, conv
|
nonlocal dia, msg, req, conv
|
||||||
try:
|
try:
|
||||||
for ans in chat(dia, msg, True, **req):
|
for ans in chat(dia, msg, True, **req):
|
||||||
ans = structure_answer(conv, ans, message_id, conv.id)
|
ans = structure_answer(conv, ans, message_id, conv.id)
|
||||||
yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n"
|
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:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n"
|
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
|
answer = None
|
||||||
for ans in chat(dia, msg, **req):
|
for ans in chat(dia, msg, **req):
|
||||||
answer = structure_answer(conv, ans, message_id, conv.id)
|
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
|
break
|
||||||
return get_json_result(data=answer)
|
return get_json_result(data=answer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -18,12 +18,14 @@ from flask import request
|
|||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from api import settings
|
from api import settings
|
||||||
|
from api.apps import smtp_mail_server
|
||||||
from api.db import UserTenantRole, StatusEnum
|
from api.db import UserTenantRole, StatusEnum
|
||||||
from api.db.db_models import UserTenant
|
from api.db.db_models import UserTenant
|
||||||
from api.db.services.user_service import UserTenantService, UserService
|
from api.db.services.user_service import UserTenantService, UserService
|
||||||
|
|
||||||
from api.utils import get_uuid, delta_seconds
|
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.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
|
@manager.route("/<tenant_id>/user/list", methods=["GET"]) # noqa: F821
|
||||||
@ -78,6 +80,20 @@ def create(tenant_id):
|
|||||||
role=UserTenantRole.INVITE,
|
role=UserTenantRole.INVITE,
|
||||||
status=StatusEnum.VALID.value)
|
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 = invite_users[0].to_dict()
|
||||||
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
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
|
# chat settings
|
||||||
"summary": False,
|
"summary": False,
|
||||||
"chat_id": "",
|
"chat_id": "",
|
||||||
|
# Leave it here for reference, don't need to set default values
|
||||||
"llm_setting": {
|
"llm_setting": {
|
||||||
"temperature": 0.1,
|
# "temperature": 0.1,
|
||||||
"top_p": 0.3,
|
# "top_p": 0.3,
|
||||||
"frequency_penalty": 0.7,
|
# "frequency_penalty": 0.7,
|
||||||
"presence_penalty": 0.4,
|
# "presence_penalty": 0.4,
|
||||||
},
|
},
|
||||||
"chat_settingcross_languages": [],
|
"chat_settingcross_languages": [],
|
||||||
"highlight": False,
|
"highlight": False,
|
||||||
@ -1020,4 +1021,4 @@ def migrate_db():
|
|||||||
migrate(migrator.add_column("dialog", "meta_data_filter", JSONField(null=True, default={})))
|
migrate(migrator.add_column("dialog", "meta_data_filter", JSONField(null=True, default={})))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
logging.disable(logging.NOTSET)
|
logging.disable(logging.NOTSET)
|
||||||
|
|||||||
@ -99,7 +99,6 @@ class DialogService(CommonService):
|
|||||||
|
|
||||||
return list(chats.dicts())
|
return list(chats.dicts())
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@DB.connection_context()
|
@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):
|
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]):
|
def meta_filter(metas: dict, filters: list[dict]):
|
||||||
doc_ids = []
|
doc_ids = []
|
||||||
|
|
||||||
def filter_out(v2docs, operator, value):
|
def filter_out(v2docs, operator, value):
|
||||||
nonlocal doc_ids
|
nonlocal doc_ids
|
||||||
for input,docids in v2docs.items():
|
for input, docids in v2docs.items():
|
||||||
try:
|
try:
|
||||||
input = float(input)
|
input = float(input)
|
||||||
value = float(value)
|
value = float(value)
|
||||||
@ -389,7 +389,17 @@ def chat(dialog, messages, stream=True, **kwargs):
|
|||||||
reasoner = DeepResearcher(
|
reasoner = DeepResearcher(
|
||||||
chat_mdl,
|
chat_mdl,
|
||||||
prompt_config,
|
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)):
|
for think in reasoner.thinking(kbinfos, " ".join(questions)):
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import uuid
|
|||||||
|
|
||||||
from werkzeug.serving import run_simple
|
from werkzeug.serving import run_simple
|
||||||
from api import settings
|
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.runtime_config import RuntimeConfig
|
||||||
from api.db.services.document_service import DocumentService
|
from api.db.services.document_service import DocumentService
|
||||||
from api import utils
|
from api import utils
|
||||||
@ -74,11 +74,11 @@ def signal_handler(sig, frame):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logging.info(r"""
|
logging.info(r"""
|
||||||
____ ___ ______ ______ __
|
____ ___ ______ ______ __
|
||||||
/ __ \ / | / ____// ____// /____ _ __
|
/ __ \ / | / ____// ____// /____ _ __
|
||||||
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
|
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
|
||||||
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
|
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
|
||||||
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
|
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
|
||||||
|
|
||||||
""")
|
""")
|
||||||
logging.info(
|
logging.info(
|
||||||
@ -137,6 +137,18 @@ if __name__ == '__main__':
|
|||||||
else:
|
else:
|
||||||
threading.Timer(1.0, delayed_start_update_progress).start()
|
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
|
# start http server
|
||||||
try:
|
try:
|
||||||
logging.info("RAGFlow HTTP server start...")
|
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"]
|
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():
|
def get_or_create_secret_key():
|
||||||
secret_key = os.environ.get("RAGFLOW_SECRET_KEY")
|
secret_key = os.environ.get("RAGFLOW_SECRET_KEY")
|
||||||
@ -186,6 +196,21 @@ def init_settings():
|
|||||||
global SANDBOX_HOST
|
global SANDBOX_HOST
|
||||||
SANDBOX_HOST = os.environ.get("SANDBOX_HOST", "sandbox-executor-manager")
|
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):
|
class CustomEnum(Enum):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -21,6 +21,9 @@ import re
|
|||||||
import socket
|
import socket
|
||||||
from urllib.parse import urlparse
|
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 import webdriver
|
||||||
from selenium.common.exceptions import TimeoutException
|
from selenium.common.exceptions import TimeoutException
|
||||||
from selenium.webdriver.chrome.options import Options
|
from selenium.webdriver.chrome.options import Options
|
||||||
@ -31,6 +34,7 @@ from selenium.webdriver.support.ui import WebDriverWait
|
|||||||
from webdriver_manager.chrome import ChromeDriverManager
|
from webdriver_manager.chrome import ChromeDriverManager
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CONTENT_TYPE_MAP = {
|
CONTENT_TYPE_MAP = {
|
||||||
# Office
|
# Office
|
||||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"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
|
return parsed if parsed > 0 else default
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return default
|
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
|
# switch: false
|
||||||
# component: false
|
# component: false
|
||||||
# dataset: false
|
# dataset: false
|
||||||
|
# smtp:
|
||||||
|
# mail_server: ""
|
||||||
|
# mail_port: 465
|
||||||
|
# mail_use_ssl: true
|
||||||
|
# mail_use_tls: false
|
||||||
|
# mail_username: ""
|
||||||
|
# mail_password: ""
|
||||||
|
# mail_default_sender:
|
||||||
|
# - "RAGFlow" # display name
|
||||||
|
# - "" # sender email address
|
||||||
|
# mail_frontend_url: "https://your-frontend.example.com"
|
||||||
|
|||||||
@ -90,9 +90,17 @@ class RAGFlowExcelParser:
|
|||||||
return wb
|
return wb
|
||||||
|
|
||||||
def html(self, fnm, chunk_rows=256):
|
def html(self, fnm, chunk_rows=256):
|
||||||
|
from html import escape
|
||||||
|
|
||||||
file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm
|
file_like_object = BytesIO(fnm) if not isinstance(fnm, str) else fnm
|
||||||
wb = RAGFlowExcelParser._load_excel_to_workbook(file_like_object)
|
wb = RAGFlowExcelParser._load_excel_to_workbook(file_like_object)
|
||||||
tb_chunks = []
|
tb_chunks = []
|
||||||
|
|
||||||
|
def _fmt(v):
|
||||||
|
if v is None:
|
||||||
|
return ""
|
||||||
|
return str(v).strip()
|
||||||
|
|
||||||
for sheetname in wb.sheetnames:
|
for sheetname in wb.sheetnames:
|
||||||
ws = wb[sheetname]
|
ws = wb[sheetname]
|
||||||
rows = list(ws.rows)
|
rows = list(ws.rows)
|
||||||
@ -101,7 +109,7 @@ class RAGFlowExcelParser:
|
|||||||
|
|
||||||
tb_rows_0 = "<tr>"
|
tb_rows_0 = "<tr>"
|
||||||
for t in list(rows[0]):
|
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>"
|
tb_rows_0 += "</tr>"
|
||||||
|
|
||||||
for chunk_i in range((len(rows) - 1) // chunk_rows + 1):
|
for chunk_i in range((len(rows) - 1) // chunk_rows + 1):
|
||||||
@ -109,7 +117,7 @@ class RAGFlowExcelParser:
|
|||||||
tb += f"<table><caption>{sheetname}</caption>"
|
tb += f"<table><caption>{sheetname}</caption>"
|
||||||
tb += tb_rows_0
|
tb += tb_rows_0
|
||||||
for r in list(
|
for r in list(
|
||||||
rows[1 + chunk_i * chunk_rows: 1 + (chunk_i + 1) * chunk_rows]
|
rows[1 + chunk_i * chunk_rows: min(1 + (chunk_i + 1) * chunk_rows, len(rows))]
|
||||||
):
|
):
|
||||||
tb += "<tr>"
|
tb += "<tr>"
|
||||||
for i, c in enumerate(r):
|
for i, c in enumerate(r):
|
||||||
|
|||||||
@ -5,7 +5,7 @@ slug: /http_api_reference
|
|||||||
|
|
||||||
# HTTP API
|
# 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
|
##### 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.
|
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.
|
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.
|
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
|
#### Response
|
||||||
@ -2675,7 +2675,7 @@ curl --request POST \
|
|||||||
|
|
||||||
- `agent_id`: (*Path parameter*)
|
- `agent_id`: (*Path parameter*)
|
||||||
The ID of the associated agent.
|
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.
|
The optional user-defined ID for parsing docs (especially images) when creating a session while uploading files.
|
||||||
|
|
||||||
#### Response
|
#### Response
|
||||||
@ -2755,7 +2755,7 @@ Success:
|
|||||||
"mode": "conversational",
|
"mode": "conversational",
|
||||||
"outputs": {},
|
"outputs": {},
|
||||||
"prologue": "Hi! I'm your assistant. What can I do for you?",
|
"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": []
|
"upstream": []
|
||||||
@ -2912,17 +2912,17 @@ Asks a specified agent a question to start an AI-powered conversation.
|
|||||||
- Body:
|
- Body:
|
||||||
- `"question"`: `string`
|
- `"question"`: `string`
|
||||||
- `"stream"`: `boolean`
|
- `"stream"`: `boolean`
|
||||||
- `"session_id"`: `string`(optional)
|
- `"session_id"`: `string` (optional)
|
||||||
- `"inputs"`: `object`(optional)
|
- `"inputs"`: `object` (optional)
|
||||||
- `"user_id"`: `string`(optional)
|
- `"user_id"`: `string` (optional)
|
||||||
|
|
||||||
:::info IMPORTANT
|
:::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
|
##### Request example
|
||||||
|
|
||||||
- If the **Begin** component does not take parameters.
|
- If the **Begin** component does not take parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl --request POST \
|
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
|
```bash
|
||||||
curl --request POST \
|
curl --request POST \
|
||||||
|
|||||||
@ -5,7 +5,7 @@ slug: /python_api_reference
|
|||||||
|
|
||||||
# Python API
|
# 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
|
:::tip NOTE
|
||||||
Run the following command to download the Python SDK:
|
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.
|
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
|
## v0.20.1
|
||||||
|
|
||||||
Released on August 8, 2025.
|
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.
|
- Unable to add models via Ollama/Xinference, an issue introduced in v0.17.1.
|
||||||
|
|
||||||
### Related APIs
|
### API changes
|
||||||
|
|
||||||
#### HTTP APIs
|
#### HTTP APIs
|
||||||
|
|
||||||
@ -243,7 +273,7 @@ The following is a screenshot of a conversation that integrates Deep Research:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Related APIs
|
### API changes
|
||||||
|
|
||||||
#### HTTP APIs
|
#### HTTP APIs
|
||||||
|
|
||||||
@ -318,7 +348,7 @@ This release fixes the following issues:
|
|||||||
- Using the **Table** parsing method results in information loss.
|
- Using the **Table** parsing method results in information loss.
|
||||||
- Miscellaneous API issues.
|
- Miscellaneous API issues.
|
||||||
|
|
||||||
### Related APIs
|
### API changes
|
||||||
|
|
||||||
#### HTTP APIs
|
#### HTTP APIs
|
||||||
|
|
||||||
@ -354,7 +384,7 @@ Released on December 18, 2024.
|
|||||||
- Upgrades the Document Layout Analysis model in DeepDoc.
|
- Upgrades the Document Layout Analysis model in DeepDoc.
|
||||||
- Significantly enhances the retrieval performance when using [Infinity](https://github.com/infiniflow/infinity) as document engine.
|
- Significantly enhances the retrieval performance when using [Infinity](https://github.com/infiniflow/infinity) as document engine.
|
||||||
|
|
||||||
### Related APIs
|
### API changes
|
||||||
|
|
||||||
#### HTTP APIs
|
#### 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.
|
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
|
#### 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.
|
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
|
#### HTTP API
|
||||||
|
|
||||||
@ -591,7 +621,7 @@ Released on May 21, 2024.
|
|||||||
- Supports monitoring of system components, including Elasticsearch, MySQL, Redis, and MinIO.
|
- 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.
|
- Supports disabling **Layout Recognition** in the GENERAL chunking method to reduce file chunking time.
|
||||||
|
|
||||||
### Related APIs
|
### API changes
|
||||||
|
|
||||||
#### HTTP API
|
#### HTTP API
|
||||||
|
|
||||||
|
|||||||
@ -44,9 +44,21 @@ spec:
|
|||||||
checksum/config-es: {{ include (print $.Template.BasePath "/elasticsearch-config.yaml") . | sha256sum }}
|
checksum/config-es: {{ include (print $.Template.BasePath "/elasticsearch-config.yaml") . | sha256sum }}
|
||||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
spec:
|
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:
|
initContainers:
|
||||||
- name: fix-data-volume-permissions
|
- 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:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@ -55,14 +67,20 @@ spec:
|
|||||||
- mountPath: /usr/share/elasticsearch/data
|
- mountPath: /usr/share/elasticsearch/data
|
||||||
name: es-data
|
name: es-data
|
||||||
- name: sysctl
|
- name: sysctl
|
||||||
image: busybox
|
image: {{ .Values.elasticsearch.initContainers.busybox.repository }}:{{ .Values.elasticsearch.initContainers.busybox.tag }}
|
||||||
|
{{- with .Values.elasticsearch.initContainers.busybox.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
runAsUser: 0
|
runAsUser: 0
|
||||||
command: ["sysctl", "-w", "vm.max_map_count=262144"]
|
command: ["sysctl", "-w", "vm.max_map_count=262144"]
|
||||||
containers:
|
containers:
|
||||||
- name: elasticsearch
|
- 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:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "ragflow.fullname" . }}-env-config
|
name: {{ include "ragflow.fullname" . }}-env-config
|
||||||
|
|||||||
@ -43,9 +43,21 @@ spec:
|
|||||||
annotations:
|
annotations:
|
||||||
checksum/config: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
spec:
|
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:
|
containers:
|
||||||
- name: infinity
|
- name: infinity
|
||||||
image: {{ .Values.infinity.image.repository }}:{{ .Values.infinity.image.tag }}
|
image: {{ .Values.infinity.image.repository }}:{{ .Values.infinity.image.tag }}
|
||||||
|
{{- with .Values.infinity.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "ragflow.fullname" . }}-env-config
|
name: {{ include "ragflow.fullname" . }}-env-config
|
||||||
|
|||||||
@ -43,9 +43,21 @@ spec:
|
|||||||
{{- include "ragflow.labels" . | nindent 8 }}
|
{{- include "ragflow.labels" . | nindent 8 }}
|
||||||
app.kubernetes.io/component: minio
|
app.kubernetes.io/component: minio
|
||||||
spec:
|
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:
|
containers:
|
||||||
- name: minio
|
- name: minio
|
||||||
image: {{ .Values.minio.image.repository }}:{{ .Values.minio.image.tag }}
|
image: {{ .Values.minio.image.repository }}:{{ .Values.minio.image.tag }}
|
||||||
|
{{- with .Values.minio.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "ragflow.fullname" . }}-env-config
|
name: {{ include "ragflow.fullname" . }}-env-config
|
||||||
|
|||||||
@ -44,9 +44,21 @@ spec:
|
|||||||
checksum/config-mysql: {{ include (print $.Template.BasePath "/mysql-config.yaml") . | sha256sum }}
|
checksum/config-mysql: {{ include (print $.Template.BasePath "/mysql-config.yaml") . | sha256sum }}
|
||||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
spec:
|
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:
|
containers:
|
||||||
- name: mysql
|
- name: mysql
|
||||||
image: {{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }}
|
image: {{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }}
|
||||||
|
{{- with .Values.mysql.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "ragflow.fullname" . }}-env-config
|
name: {{ include "ragflow.fullname" . }}-env-config
|
||||||
|
|||||||
@ -44,9 +44,21 @@ spec:
|
|||||||
checksum/config-opensearch: {{ include (print $.Template.BasePath "/opensearch-config.yaml") . | sha256sum }}
|
checksum/config-opensearch: {{ include (print $.Template.BasePath "/opensearch-config.yaml") . | sha256sum }}
|
||||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
spec:
|
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:
|
initContainers:
|
||||||
- name: fix-data-volume-permissions
|
- 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:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@ -55,7 +67,10 @@ spec:
|
|||||||
- mountPath: /usr/share/opensearch/data
|
- mountPath: /usr/share/opensearch/data
|
||||||
name: opensearch-data
|
name: opensearch-data
|
||||||
- name: sysctl
|
- name: sysctl
|
||||||
image: busybox
|
image: {{ .Values.opensearch.initContainers.busybox.repository }}:{{ .Values.opensearch.initContainers.busybox.tag }}
|
||||||
|
{{- with .Values.opensearch.initContainers.busybox.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
runAsUser: 0
|
runAsUser: 0
|
||||||
@ -63,6 +78,9 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: opensearch
|
- name: opensearch
|
||||||
image: {{ .Values.opensearch.image.repository }}:{{ .Values.opensearch.image.tag }}
|
image: {{ .Values.opensearch.image.repository }}:{{ .Values.opensearch.image.tag }}
|
||||||
|
{{- with .Values.opensearch.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "ragflow.fullname" . }}-env-config
|
name: {{ include "ragflow.fullname" . }}-env-config
|
||||||
|
|||||||
@ -25,9 +25,21 @@ spec:
|
|||||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
checksum/config-ragflow: {{ include (print $.Template.BasePath "/ragflow_config.yaml") . | sha256sum }}
|
checksum/config-ragflow: {{ include (print $.Template.BasePath "/ragflow_config.yaml") . | sha256sum }}
|
||||||
spec:
|
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:
|
containers:
|
||||||
- name: ragflow
|
- name: ragflow
|
||||||
image: {{ .Values.env.RAGFLOW_IMAGE }}
|
image: {{ .Values.ragflow.image.repository }}:{{ .Values.ragflow.image.tag }}
|
||||||
|
{{- with .Values.ragflow.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
name: http
|
name: http
|
||||||
|
|||||||
@ -40,10 +40,22 @@ spec:
|
|||||||
annotations:
|
annotations:
|
||||||
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
checksum/config-env: {{ include (print $.Template.BasePath "/env.yaml") . | sha256sum }}
|
||||||
spec:
|
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
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- name: redis
|
- name: redis
|
||||||
image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}
|
image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}
|
||||||
|
{{- with .Values.redis.image.pullPolicy }}
|
||||||
|
imagePullPolicy: {{ . }}
|
||||||
|
{{- end }}
|
||||||
command:
|
command:
|
||||||
- "sh"
|
- "sh"
|
||||||
- "-c"
|
- "-c"
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
# Based on docker compose .env file
|
# Based on docker compose .env file
|
||||||
|
|
||||||
|
# Global image pull secrets configuration
|
||||||
|
imagePullSecrets: []
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# The type of doc engine to use.
|
# The type of doc engine to use.
|
||||||
# Available options:
|
# Available options:
|
||||||
@ -32,31 +36,6 @@ env:
|
|||||||
# The password for Redis
|
# The password for Redis
|
||||||
REDIS_PASSWORD: infini_rag_flow_helm
|
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.
|
# The local time zone.
|
||||||
TIMEZONE: "Asia/Shanghai"
|
TIMEZONE: "Asia/Shanghai"
|
||||||
|
|
||||||
@ -75,7 +54,11 @@ env:
|
|||||||
EMBEDDING_BATCH_SIZE: 16
|
EMBEDDING_BATCH_SIZE: 16
|
||||||
|
|
||||||
ragflow:
|
ragflow:
|
||||||
|
image:
|
||||||
|
repository: infiniflow/ragflow
|
||||||
|
tag: v0.20.1-slim
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
# Optional service configuration overrides
|
# Optional service configuration overrides
|
||||||
# to be written to local.service_conf.yaml
|
# to be written to local.service_conf.yaml
|
||||||
# inside the RAGFlow container
|
# inside the RAGFlow container
|
||||||
@ -114,6 +97,8 @@ infinity:
|
|||||||
image:
|
image:
|
||||||
repository: infiniflow/infinity
|
repository: infiniflow/infinity
|
||||||
tag: v0.6.0-dev5
|
tag: v0.6.0-dev5
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
storage:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 5Gi
|
capacity: 5Gi
|
||||||
@ -124,6 +109,20 @@ infinity:
|
|||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
|
||||||
elasticsearch:
|
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:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 20Gi
|
capacity: 20Gi
|
||||||
@ -140,6 +139,17 @@ opensearch:
|
|||||||
image:
|
image:
|
||||||
repository: opensearchproject/opensearch
|
repository: opensearchproject/opensearch
|
||||||
tag: 2.19.1
|
tag: 2.19.1
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
|
initContainers:
|
||||||
|
alpine:
|
||||||
|
repository: alpine
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
busybox:
|
||||||
|
repository: busybox
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
storage:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 20Gi
|
capacity: 20Gi
|
||||||
@ -156,6 +166,8 @@ minio:
|
|||||||
image:
|
image:
|
||||||
repository: quay.io/minio/minio
|
repository: quay.io/minio/minio
|
||||||
tag: RELEASE.2023-12-20T01-00-02Z
|
tag: RELEASE.2023-12-20T01-00-02Z
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
storage:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 5Gi
|
capacity: 5Gi
|
||||||
@ -169,6 +181,8 @@ mysql:
|
|||||||
image:
|
image:
|
||||||
repository: mysql
|
repository: mysql
|
||||||
tag: 8.0.39
|
tag: 8.0.39
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
storage:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 5Gi
|
capacity: 5Gi
|
||||||
@ -182,6 +196,8 @@ redis:
|
|||||||
image:
|
image:
|
||||||
repository: valkey/valkey
|
repository: valkey/valkey
|
||||||
tag: 8
|
tag: 8
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
pullSecrets: []
|
||||||
storage:
|
storage:
|
||||||
className:
|
className:
|
||||||
capacity: 5Gi
|
capacity: 5Gi
|
||||||
|
|||||||
@ -130,6 +130,7 @@ dependencies = [
|
|||||||
"click>=8.1.8",
|
"click>=8.1.8",
|
||||||
"python-calamine>=0.4.0",
|
"python-calamine>=0.4.0",
|
||||||
"litellm>=1.74.15.post1",
|
"litellm>=1.74.15.post1",
|
||||||
|
"flask-mail>=0.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[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 _]
|
sections = [(_, "") for _ in excel_parser.html(binary, 12) if _]
|
||||||
else:
|
else:
|
||||||
sections = [(_, "") for _ in excel_parser(binary) if _]
|
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):
|
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.")
|
callback(0.1, "Start to parse.")
|
||||||
|
|||||||
26
uv.lock
generated
26
uv.lock
generated
@ -1,4 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
|
revision = 1
|
||||||
requires-python = ">=3.10, <3.13"
|
requires-python = ">=3.10, <3.13"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.12' and sys_platform == 'darwin'",
|
"python_full_version >= '3.12' and sys_platform == 'darwin'",
|
||||||
@ -1189,9 +1190,6 @@ name = "datrie"
|
|||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
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" }
|
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]]
|
[[package]]
|
||||||
name = "debugpy"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "flask-session"
|
name = "flask-session"
|
||||||
version = "0.8.0"
|
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/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/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/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/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/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" },
|
{ 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/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/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/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/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/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" },
|
{ 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" },
|
||||||
{ name = "flask-cors" },
|
{ name = "flask-cors" },
|
||||||
{ name = "flask-login" },
|
{ name = "flask-login" },
|
||||||
|
{ name = "flask-mail" },
|
||||||
{ name = "flask-session" },
|
{ name = "flask-session" },
|
||||||
{ name = "google-generativeai" },
|
{ name = "google-generativeai" },
|
||||||
{ name = "google-search-results" },
|
{ name = "google-search-results" },
|
||||||
@ -5447,6 +5455,7 @@ requires-dist = [
|
|||||||
{ name = "flask", specifier = "==3.0.3" },
|
{ name = "flask", specifier = "==3.0.3" },
|
||||||
{ name = "flask-cors", specifier = "==5.0.0" },
|
{ name = "flask-cors", specifier = "==5.0.0" },
|
||||||
{ name = "flask-login", specifier = "==0.6.3" },
|
{ name = "flask-login", specifier = "==0.6.3" },
|
||||||
|
{ name = "flask-mail", specifier = ">=0.10.0" },
|
||||||
{ name = "flask-session", specifier = "==0.8.0" },
|
{ name = "flask-session", specifier = "==0.8.0" },
|
||||||
{ name = "google-generativeai", specifier = ">=0.8.1,<0.9.0" },
|
{ name = "google-generativeai", specifier = ">=0.8.1,<0.9.0" },
|
||||||
{ name = "google-search-results", specifier = "==2.4.2" },
|
{ name = "google-search-results", specifier = "==2.4.2" },
|
||||||
@ -5492,7 +5501,7 @@ requires-dist = [
|
|||||||
{ name = "pyicu", specifier = ">=2.13.1,<3.0.0" },
|
{ name = "pyicu", specifier = ">=2.13.1,<3.0.0" },
|
||||||
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
||||||
{ name = "pyodbc", specifier = ">=5.2.0,<6.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 = "pypdf2", specifier = ">=3.0.1,<4.0.0" },
|
||||||
{ name = "python-calamine", specifier = ">=0.4.0" },
|
{ name = "python-calamine", specifier = ">=0.4.0" },
|
||||||
{ name = "python-dateutil", specifier = "==2.8.2" },
|
{ name = "python-dateutil", specifier = "==2.8.2" },
|
||||||
@ -5539,6 +5548,7 @@ requires-dist = [
|
|||||||
{ name = "yfinance", specifier = "==0.2.65" },
|
{ name = "yfinance", specifier = "==0.2.65" },
|
||||||
{ name = "zhipuai", specifier = "==2.0.1" },
|
{ name = "zhipuai", specifier = "==2.0.1" },
|
||||||
]
|
]
|
||||||
|
provides-extras = ["full"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
test = [
|
test = [
|
||||||
|
|||||||
@ -70,6 +70,10 @@ const KnowledgeBaseItem = ({
|
|||||||
|
|
||||||
export default KnowledgeBaseItem;
|
export default KnowledgeBaseItem;
|
||||||
|
|
||||||
|
function buildQueryVariableOptionsByShowVariable(showVariable?: boolean) {
|
||||||
|
return showVariable ? useBuildQueryVariableOptions : () => [];
|
||||||
|
}
|
||||||
|
|
||||||
export function KnowledgeBaseFormField({
|
export function KnowledgeBaseFormField({
|
||||||
showVariable = false,
|
showVariable = false,
|
||||||
}: {
|
}: {
|
||||||
@ -84,7 +88,7 @@ export function KnowledgeBaseFormField({
|
|||||||
(x) => x.parser_id !== DocumentParserType.Tag,
|
(x) => x.parser_id !== DocumentParserType.Tag,
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextOptions = useBuildQueryVariableOptions();
|
const nextOptions = buildQueryVariableOptionsByShowVariable(showVariable)();
|
||||||
|
|
||||||
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
||||||
label: x.name,
|
label: x.name,
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useFetchDocumentInfosByIds,
|
useFetchDocumentInfosByIds,
|
||||||
@ -12,17 +11,13 @@ import {
|
|||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/pages/chat/interface';
|
||||||
import MarkdownContent from '@/pages/chat/markdown-content';
|
import MarkdownContent from '@/pages/chat/markdown-content';
|
||||||
import { getExtension, isImage } from '@/utils/document-util';
|
import { Avatar, Flex, Space } from 'antd';
|
||||||
import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||||
import FileIcon from '../file-icon';
|
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
||||||
import IndentedTreeModal from '../indented-tree/modal';
|
|
||||||
import NewDocumentLink from '../new-document-link';
|
|
||||||
import { useTheme } from '../theme-provider';
|
import { useTheme } from '../theme-provider';
|
||||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
|
interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
|
||||||
item: IMessage;
|
item: IMessage;
|
||||||
reference: IReference;
|
reference: IReference;
|
||||||
@ -59,21 +54,11 @@ const MessageItem = ({
|
|||||||
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
||||||
const { data: documentThumbnails, setDocumentIds: setIds } =
|
const { data: documentThumbnails, setDocumentIds: setIds } =
|
||||||
useFetchDocumentThumbnailsByIds();
|
useFetchDocumentThumbnailsByIds();
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
|
||||||
const [clickedDocumentId, setClickedDocumentId] = useState('');
|
|
||||||
|
|
||||||
const referenceDocumentList = useMemo(() => {
|
const referenceDocumentList = useMemo(() => {
|
||||||
return reference?.doc_aggs ?? [];
|
return reference?.doc_aggs ?? [];
|
||||||
}, [reference?.doc_aggs]);
|
}, [reference?.doc_aggs]);
|
||||||
|
|
||||||
const handleUserDocumentClick = useCallback(
|
|
||||||
(id: string) => () => {
|
|
||||||
setClickedDocumentId(id);
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRegenerateMessage = useCallback(() => {
|
const handleRegenerateMessage = useCallback(() => {
|
||||||
regenerateMessage?.(item);
|
regenerateMessage?.(item);
|
||||||
}, [regenerateMessage, item]);
|
}, [regenerateMessage, item]);
|
||||||
@ -160,83 +145,18 @@ const MessageItem = ({
|
|||||||
></MarkdownContent>
|
></MarkdownContent>
|
||||||
</div>
|
</div>
|
||||||
{isAssistant && referenceDocumentList.length > 0 && (
|
{isAssistant && referenceDocumentList.length > 0 && (
|
||||||
<List
|
<ReferenceDocumentList
|
||||||
bordered
|
list={referenceDocumentList}
|
||||||
dataSource={referenceDocumentList}
|
></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>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{isUser && documentList.length > 0 && (
|
{isUser && documentList.length > 0 && (
|
||||||
<List
|
<InnerUploadedMessageFiles
|
||||||
bordered
|
files={documentList}
|
||||||
dataSource={documentList}
|
></InnerUploadedMessageFiles>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{visible && (
|
|
||||||
<IndentedTreeModal
|
|
||||||
visible={visible}
|
|
||||||
hideModal={hideModal}
|
|
||||||
documentId={clickedDocumentId}
|
|
||||||
></IndentedTreeModal>
|
|
||||||
)}
|
|
||||||
</div>
|
</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> {
|
interface UserGroupButtonProps extends Partial<IRemoveMessageById> {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { IReferenceChunk, IReferenceObject } from '@/interfaces/database/chat';
|
import { IReferenceChunk, IReferenceObject } from '@/interfaces/database/chat';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
@ -21,7 +20,6 @@ import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
|||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/pages/chat/interface';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import IndentedTreeModal from '../indented-tree/modal';
|
|
||||||
import MarkdownContent from '../next-markdown-content';
|
import MarkdownContent from '../next-markdown-content';
|
||||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||||
import { useTheme } from '../theme-provider';
|
import { useTheme } from '../theme-provider';
|
||||||
@ -79,8 +77,6 @@ function MessageItem({
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isAssistant = item.role === MessageType.Assistant;
|
const isAssistant = item.role === MessageType.Assistant;
|
||||||
const isUser = item.role === MessageType.User;
|
const isUser = item.role === MessageType.User;
|
||||||
const { visible, hideModal } = useSetModalState();
|
|
||||||
const [clickedDocumentId] = useState('');
|
|
||||||
const [showThinking, setShowThinking] = useState(false);
|
const [showThinking, setShowThinking] = useState(false);
|
||||||
const { setLastSendLoadingFunc } = useContext(AgentChatContext);
|
const { setLastSendLoadingFunc } = useContext(AgentChatContext);
|
||||||
|
|
||||||
@ -200,8 +196,6 @@ function MessageItem({
|
|||||||
sendLoading={sendLoading}
|
sendLoading={sendLoading}
|
||||||
></UserGroupButton>
|
></UserGroupButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <b>{isAssistant ? '' : nickname}</b> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -254,13 +248,6 @@ function MessageItem({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{visible && (
|
|
||||||
<IndentedTreeModal
|
|
||||||
visible={visible}
|
|
||||||
hideModal={hideModal}
|
|
||||||
documentId={clickedDocumentId}
|
|
||||||
></IndentedTreeModal>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function ReferenceDocumentList({ list }: { list: Docagg[] }) {
|
|||||||
<section className="flex gap-3 flex-wrap">
|
<section className="flex gap-3 flex-wrap">
|
||||||
{list.map((item) => (
|
{list.map((item) => (
|
||||||
<Card key={item.doc_id}>
|
<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>
|
<FileIcon id={item.doc_id} name={item.doc_name}></FileIcon>
|
||||||
<NewDocumentLink
|
<NewDocumentLink
|
||||||
documentId={item.doc_id}
|
documentId={item.doc_id}
|
||||||
|
|||||||
@ -1,34 +1,65 @@
|
|||||||
|
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||||
import { getExtension } from '@/utils/document-util';
|
import { getExtension } from '@/utils/document-util';
|
||||||
import { formatBytes } from '@/utils/file-util';
|
import { formatBytes } from '@/utils/file-util';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import FileIcon from '../file-icon';
|
||||||
|
import NewDocumentLink from '../new-document-link';
|
||||||
import SvgIcon from '../svg-icon';
|
import SvgIcon from '../svg-icon';
|
||||||
|
|
||||||
interface IProps {
|
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) {
|
export function InnerUploadedMessageFiles({ files = [] }: IProps) {
|
||||||
return (
|
return (
|
||||||
<section className="flex gap-2 pt-2">
|
<section className="flex gap-2 pt-2">
|
||||||
{files?.map((file, idx) => (
|
{files?.map((file, idx) => {
|
||||||
<div key={idx} className="flex gap-1 border rounded-md p-1.5">
|
const name = file.name;
|
||||||
{file.type.startsWith('image/') ? (
|
const isFile = file instanceof File;
|
||||||
<img
|
|
||||||
src={URL.createObjectURL(file)}
|
return (
|
||||||
alt={file.name}
|
<div key={idx} className="flex gap-1 border rounded-md p-1.5">
|
||||||
className="size-10 object-cover"
|
{!isFile ? (
|
||||||
/>
|
<FileIcon id={file.id} name={name}></FileIcon>
|
||||||
) : (
|
) : file.type.startsWith('image/') ? (
|
||||||
<SvgIcon
|
<img
|
||||||
name={`file-icon/${getExtension(file.name)}`}
|
src={URL.createObjectURL(file)}
|
||||||
width={24}
|
alt={name}
|
||||||
></SvgIcon>
|
className="size-10 object-cover"
|
||||||
)}
|
/>
|
||||||
<div className="text-xs max-w-20">
|
) : (
|
||||||
<div className="truncate">{file.name}</div>
|
<SvgIcon
|
||||||
<p className="text-text-secondary pt-1">{formatBytes(file.size)}</p>
|
name={`file-icon/${getExtension(name)}`}
|
||||||
|
width={24}
|
||||||
|
></SvgIcon>
|
||||||
|
)}
|
||||||
|
<NameWidget
|
||||||
|
name={name}
|
||||||
|
size={file.size}
|
||||||
|
id={isFile ? undefined : file.id}
|
||||||
|
></NameWidget>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export const variableEnabledFieldMap = {
|
|||||||
export enum SharedFrom {
|
export enum SharedFrom {
|
||||||
Agent = 'agent',
|
Agent = 'agent',
|
||||||
Chat = 'chat',
|
Chat = 'chat',
|
||||||
|
Search = 'search',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChatSearchParams {
|
export enum ChatSearchParams {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { useDebounce } from 'ahooks';
|
|||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import {
|
import {
|
||||||
useGetPaginationWithRouter,
|
useGetPaginationWithRouter,
|
||||||
@ -304,6 +304,9 @@ export const useSetAgent = (showMessage: boolean = true) => {
|
|||||||
// Only one file can be uploaded at a time
|
// Only one file can be uploaded at a time
|
||||||
export const useUploadCanvasFile = () => {
|
export const useUploadCanvasFile = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const canvasId = id || shared_id;
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isPending: loading,
|
isPending: loading,
|
||||||
@ -321,7 +324,7 @@ export const useUploadCanvasFile = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await agentService.uploadCanvasFile(
|
const { data } = await agentService.uploadCanvasFile(
|
||||||
{ url: api.uploadAgentFile(id), data: nextBody },
|
{ url: api.uploadAgentFile(canvasId as string), data: nextBody },
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
if (data?.code === 0) {
|
if (data?.code === 0) {
|
||||||
|
|||||||
@ -57,6 +57,18 @@ export interface IDialog {
|
|||||||
similarity_threshold: number;
|
similarity_threshold: number;
|
||||||
top_k: number;
|
top_k: number;
|
||||||
top_n: 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 {
|
export interface IConversation {
|
||||||
|
|||||||
@ -1418,6 +1418,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
createSearch: 'Create Search',
|
createSearch: 'Create Search',
|
||||||
|
searchGreeting: 'How can I help you today ?',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1185,9 +1185,13 @@ export default {
|
|||||||
knowledge: '知識',
|
knowledge: '知識',
|
||||||
chat: '聊天',
|
chat: '聊天',
|
||||||
},
|
},
|
||||||
},
|
modal: {
|
||||||
modal: {
|
okText: '確認',
|
||||||
okText: '確認',
|
cancelText: '取消',
|
||||||
cancelText: '取消',
|
},
|
||||||
|
search: {
|
||||||
|
createSearch: '新建查詢',
|
||||||
|
searchGreeting: '今天我能為你做些什麽?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1316,12 +1316,13 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
modal: {
|
||||||
modal: {
|
okText: '确认',
|
||||||
okText: '确认',
|
cancelText: '取消',
|
||||||
cancelText: '取消',
|
},
|
||||||
},
|
search: {
|
||||||
search: {
|
createSearch: '新建查询',
|
||||||
createSearch: '新建查询',
|
searchGreeting: '今天我能为你做些什么?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { FormTooltip } from '@/components/ui/tooltip';
|
|||||||
import { buildSelectOptions } from '@/utils/component-util';
|
import { buildSelectOptions } from '@/utils/component-util';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { memo } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -70,6 +70,18 @@ function BeginForm({ node }: INextOperatorForm) {
|
|||||||
name: 'enablePrologue',
|
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 {
|
const {
|
||||||
ok,
|
ok,
|
||||||
currentRecord,
|
currentRecord,
|
||||||
|
|||||||
@ -85,7 +85,7 @@ const getUrlWithToken = (token: string, from: string = 'chat') => {
|
|||||||
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useFetchTokenListBeforeOtherStep = () => {
|
export const useFetchTokenListBeforeOtherStep = () => {
|
||||||
const { showTokenEmptyError } = useShowTokenEmptyError();
|
const { showTokenEmptyError } = useShowTokenEmptyError();
|
||||||
const { showBetaEmptyError } = useShowBetaEmptyError();
|
const { showBetaEmptyError } = useShowBetaEmptyError();
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { FileUploader } from '@/components/file-uploader';
|
import { FileUploader } from '@/components/file-uploader';
|
||||||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
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 { SwitchFormField } from '@/components/switch-fom-field';
|
||||||
import { TavilyFormField } from '@/components/tavily-form-field';
|
import { TavilyFormField } from '@/components/tavily-form-field';
|
||||||
import {
|
import {
|
||||||
@ -14,11 +16,26 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
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() {
|
export default function ChatBasicSetting() {
|
||||||
const { t } = useTranslate('chat');
|
const { t } = useTranslate('chat');
|
||||||
const form = useFormContext();
|
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 (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@ -108,6 +125,18 @@ export default function ChatBasicSetting() {
|
|||||||
></SwitchFormField>
|
></SwitchFormField>
|
||||||
<TavilyFormField></TavilyFormField>
|
<TavilyFormField></TavilyFormField>
|
||||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { useEffect } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { DatasetMetadata } from '../../constants';
|
||||||
import ChatBasicSetting from './chat-basic-settings';
|
import ChatBasicSetting from './chat-basic-settings';
|
||||||
import { ChatModelSettings } from './chat-model-settings';
|
import { ChatModelSettings } from './chat-model-settings';
|
||||||
import { ChatPromptEngine } from './chat-prompt-engine';
|
import { ChatPromptEngine } from './chat-prompt-engine';
|
||||||
@ -38,6 +39,10 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
|
|||||||
top_n: 8,
|
top_n: 8,
|
||||||
vector_similarity_weight: 0.2,
|
vector_similarity_weight: 0.2,
|
||||||
top_k: 1024,
|
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(),
|
llm_id: z.string().optional(),
|
||||||
...vectorSimilarityWeightSchema,
|
...vectorSimilarityWeightSchema,
|
||||||
...topnSchema,
|
...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;
|
return formSchema;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export function SingleChatBox({ controller }: IProps) {
|
|||||||
messageContainerRef,
|
messageContainerRef,
|
||||||
sendLoading,
|
sendLoading,
|
||||||
derivedMessages,
|
derivedMessages,
|
||||||
|
isUploading,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
handlePressEnter,
|
handlePressEnter,
|
||||||
regenerateMessage,
|
regenerateMessage,
|
||||||
@ -91,6 +92,7 @@ export function SingleChatBox({ controller }: IProps) {
|
|||||||
}
|
}
|
||||||
stopOutputMessage={stopOutputMessage}
|
stopOutputMessage={stopOutputMessage}
|
||||||
onUpload={handleUploadFile}
|
onUpload={handleUploadFile}
|
||||||
|
isUploading={isUploading}
|
||||||
/>
|
/>
|
||||||
</section>
|
</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 { conversationId, isNew } = useGetChatSearchParams();
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||||
|
|
||||||
const { handleUploadFile, fileIds, clearFileIds } = useUploadFile();
|
const { handleUploadFile, fileIds, clearFileIds, isUploading } =
|
||||||
|
useUploadFile();
|
||||||
|
|
||||||
const { send, answer, done } = useSendMessageWithSse(
|
const { send, answer, done } = useSendMessageWithSse(
|
||||||
api.completeConversation,
|
api.completeConversation,
|
||||||
@ -285,5 +286,6 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
removeMessageById,
|
removeMessageById,
|
||||||
stopOutputMessage,
|
stopOutputMessage,
|
||||||
handleUploadFile,
|
handleUploadFile,
|
||||||
|
isUploading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
export function useUploadFile() {
|
export function useUploadFile() {
|
||||||
const { uploadAndParseFile } = useUploadAndParseFile();
|
const { uploadAndParseFile, loading } = useUploadAndParseFile();
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
|
|
||||||
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
||||||
@ -23,5 +23,5 @@ export function useUploadFile() {
|
|||||||
setFileIds([]);
|
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> {
|
interface IProps extends IModalProps<any> {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
chunk: IChunk | IReferenceChunk;
|
chunk: IChunk &
|
||||||
|
IReferenceChunk & { docnm_kwd: string; document_name: string };
|
||||||
}
|
}
|
||||||
function getFileExtensionRegex(filename: string): string {
|
function getFileExtensionRegex(filename: string): string {
|
||||||
const match = filename.match(/\.([^.]+)$/);
|
const match = filename.match(/\.([^.]+)$/);
|
||||||
@ -30,21 +31,22 @@ const PdfDrawer = ({
|
|||||||
// const [loaded, setLoaded] = useState(false);
|
// const [loaded, setLoaded] = useState(false);
|
||||||
const url = getDocumentUrl();
|
const url = getDocumentUrl();
|
||||||
|
|
||||||
console.log('chunk--->', chunk.docnm_kwd, url);
|
|
||||||
const [fileType, setFileType] = useState('');
|
const [fileType, setFileType] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chunk.docnm_kwd) {
|
if (chunk.docnm_kwd || chunk.document_name) {
|
||||||
const type = getFileExtensionRegex(chunk.docnm_kwd);
|
const type = getFileExtensionRegex(
|
||||||
|
chunk.docnm_kwd || chunk.document_name,
|
||||||
|
);
|
||||||
setFileType(type);
|
setFileType(type);
|
||||||
}
|
}
|
||||||
}, [chunk.docnm_kwd]);
|
}, [chunk.docnm_kwd, chunk.document_name]);
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
<FileIcon name={chunk.docnm_kwd || chunk.document_name}></FileIcon>
|
||||||
{chunk.docnm_kwd}
|
{chunk.docnm_kwd || chunk.document_name}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onCancel={hideModal}
|
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,
|
BreadcrumbSeparator,
|
||||||
} from '@/components/ui/breadcrumb';
|
} from '@/components/ui/breadcrumb';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { SharedFrom } from '@/constants/chat';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
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 { useEffect, useState } from 'react';
|
||||||
|
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
useFetchSearchDetail,
|
useFetchSearchDetail,
|
||||||
} from '../next-searches/hooks';
|
} from '../next-searches/hooks';
|
||||||
|
import EmbedAppModal from './embed-app-modal';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import SearchHome from './search-home';
|
import SearchHome from './search-home';
|
||||||
import { SearchSetting } from './search-setting';
|
import { SearchSetting } from './search-setting';
|
||||||
@ -24,9 +28,15 @@ export default function SearchPage() {
|
|||||||
const { navigateToSearchList } = useNavigatePage();
|
const { navigateToSearchList } = useNavigatePage();
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const { data: SearchData } = useFetchSearchDetail();
|
const { data: SearchData } = useFetchSearchDetail();
|
||||||
|
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||||
const [openSetting, setOpenSetting] = useState(false);
|
const [openSetting, setOpenSetting] = useState(false);
|
||||||
|
const [openEmbed, setOpenEmbed] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const { data: tenantInfo } = useFetchTenantInfo();
|
||||||
|
const tenantId = tenantInfo.tenant_id;
|
||||||
|
useEffect(() => {
|
||||||
|
handleOperate();
|
||||||
|
}, [handleOperate]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
setOpenSetting(false);
|
setOpenSetting(false);
|
||||||
@ -81,8 +91,37 @@ export default function SearchPage() {
|
|||||||
data={SearchData as ISearchAppDetailProps}
|
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>
|
</div>
|
||||||
|
|
||||||
{!isSearching && (
|
{!isSearching && (
|
||||||
<div className="absolute left-5 bottom-12 ">
|
<div className="absolute left-5 bottom-12 ">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const percent = usePendingMindMap();
|
const percent = usePendingMindMap();
|
||||||
return (
|
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="flex w-full justify-between items-center mb-2">
|
||||||
<div className="text-text-primary font-medium text-base">
|
<div className="text-text-primary font-medium text-base">
|
||||||
{t('chunk.mind')}
|
{t('chunk.mind')}
|
||||||
@ -32,11 +32,14 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && (
|
{!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
|
<IndentedTree
|
||||||
data={data}
|
data={data}
|
||||||
show
|
show
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
></IndentedTree>
|
></IndentedTree>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Input } from '@/components/originui/input';
|
import { Input } from '@/components/originui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
@ -68,42 +67,6 @@ export default function SearchPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import {
|
import {
|
||||||
MultiSelect,
|
MultiSelect,
|
||||||
MultiSelectOptionType,
|
MultiSelectOptionType,
|
||||||
@ -42,6 +41,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
IUpdateSearchProps,
|
IUpdateSearchProps,
|
||||||
|
IllmSettingProps,
|
||||||
useUpdateSearch,
|
useUpdateSearch,
|
||||||
} from '../next-searches/hooks';
|
} from '../next-searches/hooks';
|
||||||
import {
|
import {
|
||||||
@ -55,14 +55,6 @@ interface SearchSettingProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
data: ISearchAppDetailProps;
|
data: ISearchAppDetailProps;
|
||||||
}
|
}
|
||||||
interface ISubmitLlmSettingProps {
|
|
||||||
llm_id: string;
|
|
||||||
parameter: string;
|
|
||||||
temperature?: number;
|
|
||||||
top_p?: number;
|
|
||||||
frequency_penalty?: number;
|
|
||||||
presence_penalty?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchSettingFormSchema = z
|
const SearchSettingFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -120,16 +112,19 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
||||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||||
|
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
||||||
const resetForm = useCallback(() => {
|
const resetForm = useCallback(() => {
|
||||||
formMethods.reset({
|
formMethods.reset({
|
||||||
search_id: data?.id,
|
search_id: data?.id,
|
||||||
name: data?.name || '',
|
name: data?.name || '',
|
||||||
avatar: data?.avatar || '',
|
avatar: data?.avatar || '',
|
||||||
description: data?.description || 'You are an intelligent assistant.',
|
description: data?.description || descriptionDefaultValue,
|
||||||
search_config: {
|
search_config: {
|
||||||
kb_ids: search_config?.kb_ids || [],
|
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,
|
web_search: search_config?.web_search || false,
|
||||||
doc_ids: [],
|
doc_ids: [],
|
||||||
similarity_threshold: 0.0,
|
similarity_threshold: 0.0,
|
||||||
@ -198,8 +193,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [avatarFile]);
|
}, [avatarFile]);
|
||||||
const { list: datasetListOrigin, loading: datasetLoading } =
|
const { list: datasetListOrigin } = useFetchKnowledgeList();
|
||||||
useFetchKnowledgeList();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
||||||
@ -259,7 +253,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const { search_config, ...other_formdata } = formData;
|
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 = {
|
const llmSetting = {
|
||||||
llm_id: llm_setting.llm_id,
|
llm_id: llm_setting.llm_id,
|
||||||
parameter: llm_setting.parameter,
|
parameter: llm_setting.parameter,
|
||||||
@ -267,7 +262,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
top_p: llm_setting.top_p,
|
top_p: llm_setting.top_p,
|
||||||
frequency_penalty: llm_setting.frequency_penalty,
|
frequency_penalty: llm_setting.frequency_penalty,
|
||||||
presence_penalty: llm_setting.presence_penalty,
|
presence_penalty: llm_setting.presence_penalty,
|
||||||
} as ISubmitLlmSettingProps;
|
} as IllmSettingProps;
|
||||||
|
|
||||||
if (!llm_setting.frequencyPenaltyEnabled) {
|
if (!llm_setting.frequencyPenaltyEnabled) {
|
||||||
delete llmSetting.frequency_penalty;
|
delete llmSetting.frequency_penalty;
|
||||||
}
|
}
|
||||||
@ -284,6 +280,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
...other_formdata,
|
...other_formdata,
|
||||||
search_config: {
|
search_config: {
|
||||||
...other_config,
|
...other_config,
|
||||||
|
vector_similarity_weight: 1 - vector_similarity_weight,
|
||||||
llm_setting: { ...llmSetting },
|
llm_setting: { ...llmSetting },
|
||||||
},
|
},
|
||||||
tenant_id: systemSetting.tenant_id,
|
tenant_id: systemSetting.tenant_id,
|
||||||
@ -355,46 +352,54 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Avatar</FormLabel>
|
<FormLabel>Avatar</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative group">
|
<div className="relative group flex items-end gap-2">
|
||||||
{!avatarBase64Str ? (
|
<div>
|
||||||
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
{!avatarBase64Str ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
||||||
<Upload />
|
<div className="flex flex-col items-center">
|
||||||
<p>{t('common.upload')}</p>
|
<Upload />
|
||||||
|
<p>{t('common.upload')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
||||||
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
<RAGFlowAvatar
|
||||||
<RAGFlowAvatar
|
avatar={avatarBase64Str}
|
||||||
avatar={avatarBase64Str}
|
name={data.name}
|
||||||
name={data.name}
|
className="w-[64px] h-[64px] rounded-md block"
|
||||||
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="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>
|
||||||
</div>
|
)}
|
||||||
)}
|
<input
|
||||||
<input
|
placeholder=""
|
||||||
placeholder=""
|
// {...field}
|
||||||
// {...field}
|
type="file"
|
||||||
type="file"
|
title=""
|
||||||
title=""
|
accept="image/*"
|
||||||
accept="image/*"
|
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
||||||
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
onChange={(ev) => {
|
||||||
onChange={(ev) => {
|
const file = ev.target?.files?.[0];
|
||||||
const file = ev.target?.files?.[0];
|
if (
|
||||||
if (
|
/\.(jpg|jpeg|png|webp|bmp)$/i.test(
|
||||||
/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')
|
file?.name ?? '',
|
||||||
) {
|
)
|
||||||
setAvatarFile(file!);
|
) {
|
||||||
}
|
setAvatarFile(file!);
|
||||||
ev.target.value = '';
|
}
|
||||||
}}
|
ev.target.value = '';
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="margin-1 text-muted-foreground">
|
||||||
|
{t('knowledgeConfiguration.photoTip')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -410,7 +415,20 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -451,26 +469,58 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="search_config.vector_similarity_weight"
|
name="search_config.vector_similarity_weight"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
<span className="text-destructive mr-1"> *</span>Keyword
|
<span className="text-destructive mr-1"> *</span>Keyword
|
||||||
Similarity Weight
|
Similarity Weight
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<div
|
||||||
<div className="flex justify-between items-center">
|
className={cn(
|
||||||
|
'flex items-center gap-4 justify-between',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
max={100}
|
{...field}
|
||||||
step={1}
|
max={1}
|
||||||
value={field.value as number}
|
min={0}
|
||||||
onChange={(values) => field.onChange(values)}
|
step={0.01}
|
||||||
></SingleFormSlider>
|
></SingleFormSlider>
|
||||||
<Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
</FormControl>
|
||||||
{field.value}
|
<FormControl>
|
||||||
</Label>
|
<Input
|
||||||
</div>
|
type={'number'}
|
||||||
</FormControl>
|
className="h-7 w-20 bg-bg-card"
|
||||||
|
max={1}
|
||||||
|
min={0}
|
||||||
|
step={0.01}
|
||||||
|
{...field}
|
||||||
|
></Input>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</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>
|
<FormControl>
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
{...field}
|
{...field}
|
||||||
max={100}
|
max={2048}
|
||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
></SingleFormSlider>
|
></SingleFormSlider>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type={'number'}
|
|
||||||
className="h-7 w-20 bg-bg-card"
|
className="h-7 w-20 bg-bg-card"
|
||||||
max={100}
|
max={2048}
|
||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
{...field}
|
{...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 { Dispatch, SetStateAction } from 'react';
|
||||||
import { ImageWithPopover } from '@/components/image';
|
import { useTranslation } from 'react-i18next';
|
||||||
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 { ISearchAppDetailProps } from '../next-searches/hooks';
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
import { useSendQuestion, useShowMindMapDrawer } from '../search/hooks';
|
import { useSearching } from './hooks';
|
||||||
import PdfDrawer from './document-preview-modal';
|
|
||||||
import HightLightMarkdown from './highlight-markdown';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import styles from './index.less';
|
import SearchingView from './search-view';
|
||||||
import MarkdownContent from './markdown-content';
|
|
||||||
import MindMapDrawer from './mindmap-drawer';
|
|
||||||
import RetrievalDocuments from './retrieval-documents';
|
|
||||||
export default function SearchingPage({
|
export default function SearchingPage({
|
||||||
searchText,
|
searchText,
|
||||||
data: searchData,
|
data: searchData,
|
||||||
setIsSearching,
|
setIsSearching,
|
||||||
|
setSearchText,
|
||||||
}: {
|
}: {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
||||||
setSearchText: Dispatch<SetStateAction<string>>;
|
setSearchText: Dispatch<SetStateAction<string>>;
|
||||||
data: ISearchAppDetailProps;
|
data: ISearchAppDetailProps;
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const searchingParam = useSearching({
|
||||||
const {
|
searchText,
|
||||||
sendQuestion,
|
data: searchData,
|
||||||
handleClickRelatedQuestion,
|
setIsSearching,
|
||||||
handleSearchStrChange,
|
setSearchText,
|
||||||
handleTestChunk,
|
});
|
||||||
setSelectedDocumentIds,
|
const { t } = useTranslation();
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<SearchingView
|
||||||
className={cn(
|
{...searchingParam}
|
||||||
'relative w-full flex transition-all justify-start items-center',
|
searchData={searchData}
|
||||||
)}
|
setIsSearching={setIsSearching}
|
||||||
>
|
t={t}
|
||||||
{/* 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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
|
|
||||||
interface CreateSearchProps {
|
interface CreateSearchProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -156,13 +156,13 @@ export const useDeleteSearch = () => {
|
|||||||
return { data, isError, deleteSearch };
|
return { data, isError, deleteSearch };
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IllmSettingProps {
|
export interface IllmSettingProps {
|
||||||
llm_id: string;
|
llm_id: string;
|
||||||
parameter: string;
|
parameter: string;
|
||||||
temperature: number;
|
temperature?: number;
|
||||||
top_p: number;
|
top_p?: number;
|
||||||
frequency_penalty: number;
|
frequency_penalty?: number;
|
||||||
presence_penalty: number;
|
presence_penalty?: number;
|
||||||
}
|
}
|
||||||
interface IllmSettingEnableProps {
|
interface IllmSettingEnableProps {
|
||||||
temperatureEnabled?: boolean;
|
temperatureEnabled?: boolean;
|
||||||
@ -204,14 +204,29 @@ interface SearchDetailResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFetchSearchDetail = () => {
|
export const useFetchSearchDetail = (tenantId?: string) => {
|
||||||
const { id } = useParams();
|
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>({
|
const { data, isLoading, isError } = useQuery<SearchDetailResponse, Error>({
|
||||||
queryKey: ['searchDetail', id],
|
queryKey: ['searchDetail', searchId],
|
||||||
|
enabled: !shared_id || !!tenantId,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data: response } = await searchService.getSearchDetail({
|
const { data: response } = await fetchSearchDetailFunc(param);
|
||||||
search_id: id,
|
|
||||||
});
|
|
||||||
if (response.code !== 0) {
|
if (response.code !== 0) {
|
||||||
throw new Error(response.message || 'Failed to fetch search detail');
|
throw new Error(response.message || 'Failed to fetch search detail');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export default function SearchList() {
|
|||||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="w-full h-full flex flex-col">
|
||||||
<div className="px-8 pt-8">
|
<div className="px-8 pt-8">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
icon={
|
icon={
|
||||||
@ -89,18 +89,23 @@ export default function SearchList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</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">
|
<div className="flex-1">
|
||||||
{list?.data.search_apps.map((x) => {
|
<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">
|
||||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
{list?.data.search_apps.map((x) => {
|
||||||
})}
|
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{list?.data.total && (
|
{list?.data.total && list?.data.total > 0 && (
|
||||||
<RAGFlowPagination
|
<div className="px-8 mb-4">
|
||||||
{...pick(searchParams, 'current', 'pageSize')}
|
<RAGFlowPagination
|
||||||
total={list?.data.total}
|
{...pick(searchParams, 'current', 'pageSize')}
|
||||||
onChange={handlePageChange}
|
total={list?.data.total}
|
||||||
/>
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={openCreateModal}
|
open={openCreateModal}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export function SearchCard({ data }: IProps) {
|
|||||||
navigateToSearch(data?.id);
|
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">
|
<div className="flex justify-between mb-4">
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
className="w-[32px] h-[32px]"
|
className="w-[32px] h-[32px]"
|
||||||
@ -27,7 +27,7 @@ export function SearchCard({ data }: IProps) {
|
|||||||
name={data.name}
|
name={data.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<section className="flex justify-between">
|
||||||
<div className="text-[20px] font-bold w-80% leading-5">
|
<div className="text-[20px] font-bold w-80% leading-5">
|
||||||
{data.name}
|
{data.name}
|
||||||
@ -37,22 +37,13 @@ export function SearchCard({ data }: IProps) {
|
|||||||
</SearchDropdown>
|
</SearchDropdown>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div>{data.description}</div>
|
<section className="flex flex-col gap-1 mt-1">
|
||||||
<section className="flex justify-between">
|
<div>{data.description}</div>
|
||||||
<div>
|
<div>
|
||||||
Search app
|
|
||||||
<p className="text-sm opacity-80">
|
<p className="text-sm opacity-80">
|
||||||
{formatDate(data.update_time)}
|
{formatDate(data.update_time)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</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 { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import {
|
import {
|
||||||
useTestChunkAllRetrieval,
|
useTestChunkAllRetrieval,
|
||||||
@ -18,17 +18,23 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} 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(
|
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 [sendingLoading, setSendingLoading] = useState(false);
|
||||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||||
useFetchRelatedQuestions();
|
useFetchRelatedQuestions(tenantId);
|
||||||
const [searchStr, setSearchStr] = useState<string>('');
|
const [searchStr, setSearchStr] = useState<string>('');
|
||||||
const [isFirstRender, setIsFirstRender] = useState(true);
|
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||||
@ -43,7 +49,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|||||||
setIsFirstRender(false);
|
setIsFirstRender(false);
|
||||||
setCurrentAnswer({} as IAnswer);
|
setCurrentAnswer({} as IAnswer);
|
||||||
setSendingLoading(true);
|
setSendingLoading(true);
|
||||||
send({ kb_ids: kbIds, question: q });
|
send({ kb_ids: kbIds, question: q, tenantId });
|
||||||
testChunk({
|
testChunk({
|
||||||
kb_id: kbIds,
|
kb_id: kbIds,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
@ -61,6 +67,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|||||||
fetchRelatedQuestions,
|
fetchRelatedQuestions,
|
||||||
setPagination,
|
setPagination,
|
||||||
pagination.pageSize,
|
pagination.pageSize,
|
||||||
|
tenantId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -218,7 +225,7 @@ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
|
|||||||
fetchMindMap,
|
fetchMindMap,
|
||||||
data: mindMap,
|
data: mindMap,
|
||||||
loading: mindMapLoading,
|
loading: mindMapLoading,
|
||||||
} = useFetchMindMap();
|
} = useSearchFetchMindMap();
|
||||||
|
|
||||||
const handleShowModal = useCallback(() => {
|
const handleShowModal = useCallback(() => {
|
||||||
const searchParams = { question: trim(question), kb_ids: kbIds };
|
const searchParams = { question: trim(question), kb_ids: kbIds };
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export enum Routes {
|
|||||||
AgentList = '/agent-list',
|
AgentList = '/agent-list',
|
||||||
Searches = '/next-searches',
|
Searches = '/next-searches',
|
||||||
Search = '/next-search',
|
Search = '/next-search',
|
||||||
|
SearchShare = '/next-search/share',
|
||||||
Chats = '/next-chats',
|
Chats = '/next-chats',
|
||||||
Chat = '/next-chat',
|
Chat = '/next-chat',
|
||||||
Files = '/files',
|
Files = '/files',
|
||||||
@ -234,6 +235,11 @@ const routes = [
|
|||||||
layout: false,
|
layout: false,
|
||||||
component: `@/pages${Routes.Search}`,
|
component: `@/pages${Routes.Search}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.SearchShare}`,
|
||||||
|
layout: false,
|
||||||
|
component: `@/pages${Routes.SearchShare}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Agents,
|
path: Routes.Agents,
|
||||||
layout: false,
|
layout: false,
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const {
|
|||||||
listTagByKnowledgeIds,
|
listTagByKnowledgeIds,
|
||||||
setMeta,
|
setMeta,
|
||||||
getMeta,
|
getMeta,
|
||||||
|
retrievalTestShare,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -164,6 +165,10 @@ const methods = {
|
|||||||
url: getMeta,
|
url: getMeta,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
retrievalTestShare: {
|
||||||
|
url: retrievalTestShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const kbService = registerServer<keyof typeof methods>(methods, request);
|
const kbService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|||||||
@ -8,6 +8,10 @@ const {
|
|||||||
deleteSearch,
|
deleteSearch,
|
||||||
getSearchDetail,
|
getSearchDetail,
|
||||||
updateSearchSetting,
|
updateSearchSetting,
|
||||||
|
askShare,
|
||||||
|
mindmapShare,
|
||||||
|
getRelatedQuestionsShare,
|
||||||
|
getSearchDetailShare,
|
||||||
} = api;
|
} = api;
|
||||||
const methods = {
|
const methods = {
|
||||||
createSearch: {
|
createSearch: {
|
||||||
@ -27,6 +31,23 @@ const methods = {
|
|||||||
url: updateSearchSetting,
|
url: updateSearchSetting,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
askShare: {
|
||||||
|
url: askShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
mindmapShare: {
|
||||||
|
url: mindmapShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
getRelatedQuestionsShare: {
|
||||||
|
url: getRelatedQuestionsShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
|
||||||
|
getSearchDetailShare: {
|
||||||
|
url: getSearchDetailShare,
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|
||||||
|
|||||||
@ -181,5 +181,10 @@ export default {
|
|||||||
getSearchList: `${api_host}/search/list`,
|
getSearchList: `${api_host}/search/list`,
|
||||||
deleteSearch: `${api_host}/search/rm`,
|
deleteSearch: `${api_host}/search/rm`,
|
||||||
getSearchDetail: `${api_host}/search/detail`,
|
getSearchDetail: `${api_host}/search/detail`,
|
||||||
|
getSearchDetailShare: `${ExternalApi}${api_host}/searchbots/detail`,
|
||||||
updateSearchSetting: `${api_host}/search/update`,
|
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