Merge branch 'main' of https://github.com/infiniflow/ragflow into refa/clean_agent_with_tools_sync
This commit is contained in:
commit
ed073c005e
29 changed files with 468 additions and 85 deletions
|
|
@ -91,9 +91,6 @@ class Graph:
|
||||||
def load(self):
|
def load(self):
|
||||||
self.components = self.dsl["components"]
|
self.components = self.dsl["components"]
|
||||||
cpn_nms = set([])
|
cpn_nms = set([])
|
||||||
for k, cpn in self.components.items():
|
|
||||||
cpn_nms.add(cpn["obj"]["component_name"])
|
|
||||||
|
|
||||||
for k, cpn in self.components.items():
|
for k, cpn in self.components.items():
|
||||||
cpn_nms.add(cpn["obj"]["component_name"])
|
cpn_nms.add(cpn["obj"]["component_name"])
|
||||||
param = component_class(cpn["obj"]["component_name"] + "Param")()
|
param = component_class(cpn["obj"]["component_name"] + "Param")()
|
||||||
|
|
|
||||||
|
|
@ -321,9 +321,7 @@ async def update_doc(tenant_id, dataset_id, document_id):
|
||||||
try:
|
try:
|
||||||
if not DocumentService.update_by_id(doc.id, {"status": str(status)}):
|
if not DocumentService.update_by_id(doc.id, {"status": str(status)}):
|
||||||
return get_error_data_result(message="Database error (Document update)!")
|
return get_error_data_result(message="Database error (Document update)!")
|
||||||
|
|
||||||
settings.docStoreConn.update({"doc_id": doc.id}, {"available_int": status}, search.index_name(kb.tenant_id), doc.kb_id)
|
settings.docStoreConn.update({"doc_id": doc.id}, {"available_int": status}, search.index_name(kb.tenant_id), doc.kb_id)
|
||||||
return get_result(data=True)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return server_error_response(e)
|
return server_error_response(e)
|
||||||
|
|
||||||
|
|
@ -350,12 +348,10 @@ async def update_doc(tenant_id, dataset_id, document_id):
|
||||||
}
|
}
|
||||||
renamed_doc = {}
|
renamed_doc = {}
|
||||||
for key, value in doc.to_dict().items():
|
for key, value in doc.to_dict().items():
|
||||||
if key == "run":
|
|
||||||
renamed_doc["run"] = run_mapping.get(str(value))
|
|
||||||
new_key = key_mapping.get(key, key)
|
new_key = key_mapping.get(key, key)
|
||||||
renamed_doc[new_key] = value
|
renamed_doc[new_key] = value
|
||||||
if key == "run":
|
if key == "run":
|
||||||
renamed_doc["run"] = run_mapping.get(value)
|
renamed_doc["run"] = run_mapping.get(str(value))
|
||||||
|
|
||||||
return get_result(data=renamed_doc)
|
return get_result(data=renamed_doc)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ class Storage(Enum):
|
||||||
AWS_S3 = 4
|
AWS_S3 = 4
|
||||||
OSS = 5
|
OSS = 5
|
||||||
OPENDAL = 6
|
OPENDAL = 6
|
||||||
|
GCS = 7
|
||||||
|
|
||||||
# environment
|
# environment
|
||||||
# ENV_STRONG_TEST_COUNT = "STRONG_TEST_COUNT"
|
# ENV_STRONG_TEST_COUNT = "STRONG_TEST_COUNT"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import rag.utils.ob_conn
|
||||||
import rag.utils.opensearch_conn
|
import rag.utils.opensearch_conn
|
||||||
from rag.utils.azure_sas_conn import RAGFlowAzureSasBlob
|
from rag.utils.azure_sas_conn import RAGFlowAzureSasBlob
|
||||||
from rag.utils.azure_spn_conn import RAGFlowAzureSpnBlob
|
from rag.utils.azure_spn_conn import RAGFlowAzureSpnBlob
|
||||||
|
from rag.utils.gcs_conn import RAGFlowGCS
|
||||||
from rag.utils.minio_conn import RAGFlowMinio
|
from rag.utils.minio_conn import RAGFlowMinio
|
||||||
from rag.utils.opendal_conn import OpenDALStorage
|
from rag.utils.opendal_conn import OpenDALStorage
|
||||||
from rag.utils.s3_conn import RAGFlowS3
|
from rag.utils.s3_conn import RAGFlowS3
|
||||||
|
|
@ -109,6 +110,7 @@ MINIO = {}
|
||||||
OB = {}
|
OB = {}
|
||||||
OSS = {}
|
OSS = {}
|
||||||
OS = {}
|
OS = {}
|
||||||
|
GCS = {}
|
||||||
|
|
||||||
DOC_MAXIMUM_SIZE: int = 128 * 1024 * 1024
|
DOC_MAXIMUM_SIZE: int = 128 * 1024 * 1024
|
||||||
DOC_BULK_SIZE: int = 4
|
DOC_BULK_SIZE: int = 4
|
||||||
|
|
@ -151,7 +153,8 @@ class StorageFactory:
|
||||||
Storage.AZURE_SAS: RAGFlowAzureSasBlob,
|
Storage.AZURE_SAS: RAGFlowAzureSasBlob,
|
||||||
Storage.AWS_S3: RAGFlowS3,
|
Storage.AWS_S3: RAGFlowS3,
|
||||||
Storage.OSS: RAGFlowOSS,
|
Storage.OSS: RAGFlowOSS,
|
||||||
Storage.OPENDAL: OpenDALStorage
|
Storage.OPENDAL: OpenDALStorage,
|
||||||
|
Storage.GCS: RAGFlowGCS,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -250,7 +253,7 @@ def init_settings():
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Not supported doc engine: {DOC_ENGINE}")
|
raise Exception(f"Not supported doc engine: {DOC_ENGINE}")
|
||||||
|
|
||||||
global AZURE, S3, MINIO, OSS
|
global AZURE, S3, MINIO, OSS, GCS
|
||||||
if STORAGE_IMPL_TYPE in ['AZURE_SPN', 'AZURE_SAS']:
|
if STORAGE_IMPL_TYPE in ['AZURE_SPN', 'AZURE_SAS']:
|
||||||
AZURE = get_base_config("azure", {})
|
AZURE = get_base_config("azure", {})
|
||||||
elif STORAGE_IMPL_TYPE == 'AWS_S3':
|
elif STORAGE_IMPL_TYPE == 'AWS_S3':
|
||||||
|
|
@ -259,6 +262,8 @@ def init_settings():
|
||||||
MINIO = decrypt_database_config(name="minio")
|
MINIO = decrypt_database_config(name="minio")
|
||||||
elif STORAGE_IMPL_TYPE == 'OSS':
|
elif STORAGE_IMPL_TYPE == 'OSS':
|
||||||
OSS = get_base_config("oss", {})
|
OSS = get_base_config("oss", {})
|
||||||
|
elif STORAGE_IMPL_TYPE == 'GCS':
|
||||||
|
GCS = get_base_config("gcs", {})
|
||||||
|
|
||||||
global STORAGE_IMPL
|
global STORAGE_IMPL
|
||||||
STORAGE_IMPL = StorageFactory.create(Storage[STORAGE_IMPL_TYPE])
|
STORAGE_IMPL = StorageFactory.create(Storage[STORAGE_IMPL_TYPE])
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ user_default_llm:
|
||||||
# access_key: 'access_key'
|
# access_key: 'access_key'
|
||||||
# secret_key: 'secret_key'
|
# secret_key: 'secret_key'
|
||||||
# region: 'region'
|
# region: 'region'
|
||||||
|
#gcs:
|
||||||
|
# bucket: 'bridgtl-edm-d-bucket-ragflow'
|
||||||
# oss:
|
# oss:
|
||||||
# access_key: 'access_key'
|
# access_key: 'access_key'
|
||||||
# secret_key: 'secret_key'
|
# secret_key: 'secret_key'
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,11 @@ class Pdf(PdfParser):
|
||||||
|
|
||||||
# (A) Add text
|
# (A) Add text
|
||||||
for b in self.boxes:
|
for b in self.boxes:
|
||||||
if not (from_page < b["page_number"] <= to_page + from_page):
|
# b["page_number"] is relative page number,must + from_page
|
||||||
|
global_page_num = b["page_number"] + from_page
|
||||||
|
if not (from_page < global_page_num <= to_page + from_page):
|
||||||
continue
|
continue
|
||||||
page_items[b["page_number"]].append({
|
page_items[global_page_num].append({
|
||||||
"top": b["top"],
|
"top": b["top"],
|
||||||
"x0": b["x0"],
|
"x0": b["x0"],
|
||||||
"text": b["text"],
|
"text": b["text"],
|
||||||
|
|
@ -100,7 +102,6 @@ class Pdf(PdfParser):
|
||||||
if not positions:
|
if not positions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle content type (list vs str)
|
|
||||||
if isinstance(content, list):
|
if isinstance(content, list):
|
||||||
final_text = "\n".join(content)
|
final_text = "\n".join(content)
|
||||||
elif isinstance(content, str):
|
elif isinstance(content, str):
|
||||||
|
|
@ -109,10 +110,11 @@ class Pdf(PdfParser):
|
||||||
final_text = str(content)
|
final_text = str(content)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Parse positions
|
|
||||||
pn_index = positions[0][0]
|
pn_index = positions[0][0]
|
||||||
if isinstance(pn_index, list):
|
if isinstance(pn_index, list):
|
||||||
pn_index = pn_index[0]
|
pn_index = pn_index[0]
|
||||||
|
|
||||||
|
# pn_index in tbls is absolute page number
|
||||||
current_page_num = int(pn_index) + 1
|
current_page_num = int(pn_index) + 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error parsing position: {e}")
|
print(f"Error parsing position: {e}")
|
||||||
|
|
|
||||||
207
rag/utils/gcs_conn.py
Normal file
207
rag/utils/gcs_conn.py
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
from io import BytesIO
|
||||||
|
from google.cloud import storage
|
||||||
|
from google.api_core.exceptions import NotFound
|
||||||
|
from common.decorator import singleton
|
||||||
|
from common import settings
|
||||||
|
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class RAGFlowGCS:
|
||||||
|
def __init__(self):
|
||||||
|
self.client = None
|
||||||
|
self.bucket_name = None
|
||||||
|
self.__open__()
|
||||||
|
|
||||||
|
def __open__(self):
|
||||||
|
try:
|
||||||
|
if self.client:
|
||||||
|
self.client = None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.client = storage.Client()
|
||||||
|
self.bucket_name = settings.GCS["bucket"]
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Fail to connect to GCS")
|
||||||
|
|
||||||
|
def _get_blob_path(self, folder, filename):
|
||||||
|
"""Helper to construct the path: folder/filename"""
|
||||||
|
if not folder:
|
||||||
|
return filename
|
||||||
|
return f"{folder}/{filename}"
|
||||||
|
|
||||||
|
def health(self):
|
||||||
|
folder, fnm, binary = "ragflow-health", "health_check", b"_t@@@1"
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
if not bucket_obj.exists():
|
||||||
|
logging.error(f"Health check failed: Main bucket '{self.bucket_name}' does not exist.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
blob_path = self._get_blob_path(folder, fnm)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
blob.upload_from_file(BytesIO(binary), content_type='application/octet-stream')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(f"Health check failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def put(self, bucket, fnm, binary, tenant_id=None):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket (to match interface)
|
||||||
|
for _ in range(3):
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
blob_path = self._get_blob_path(bucket, fnm)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
|
||||||
|
blob.upload_from_file(BytesIO(binary), content_type='application/octet-stream')
|
||||||
|
return True
|
||||||
|
except NotFound:
|
||||||
|
logging.error(f"Fail to put: Main bucket {self.bucket_name} does not exist.")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to put {bucket}/{fnm}:")
|
||||||
|
self.__open__()
|
||||||
|
time.sleep(1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def rm(self, bucket, fnm, tenant_id=None):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
blob_path = self._get_blob_path(bucket, fnm)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
blob.delete()
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to remove {bucket}/{fnm}:")
|
||||||
|
|
||||||
|
def get(self, bucket, filename, tenant_id=None):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
for _ in range(1):
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
blob_path = self._get_blob_path(bucket, filename)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
return blob.download_as_bytes()
|
||||||
|
except NotFound:
|
||||||
|
logging.warning(f"File not found {bucket}/{filename} in {self.bucket_name}")
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to get {bucket}/{filename}")
|
||||||
|
self.__open__()
|
||||||
|
time.sleep(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def obj_exist(self, bucket, filename, tenant_id=None):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
blob_path = self._get_blob_path(bucket, filename)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
return blob.exists()
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"obj_exist {bucket}/{filename} got exception")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def bucket_exists(self, bucket):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
return bucket_obj.exists()
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"bucket_exist check for {self.bucket_name} got exception")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_presigned_url(self, bucket, fnm, expires, tenant_id=None):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
for _ in range(10):
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
blob_path = self._get_blob_path(bucket, fnm)
|
||||||
|
blob = bucket_obj.blob(blob_path)
|
||||||
|
|
||||||
|
expiration = expires
|
||||||
|
if isinstance(expires, int):
|
||||||
|
expiration = datetime.timedelta(seconds=expires)
|
||||||
|
|
||||||
|
url = blob.generate_signed_url(
|
||||||
|
version="v4",
|
||||||
|
expiration=expiration,
|
||||||
|
method="GET"
|
||||||
|
)
|
||||||
|
return url
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to get_presigned {bucket}/{fnm}:")
|
||||||
|
self.__open__()
|
||||||
|
time.sleep(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def remove_bucket(self, bucket):
|
||||||
|
# RENAMED PARAMETER: bucket_name -> bucket
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
prefix = f"{bucket}/"
|
||||||
|
|
||||||
|
blobs = list(self.client.list_blobs(self.bucket_name, prefix=prefix))
|
||||||
|
|
||||||
|
if blobs:
|
||||||
|
bucket_obj.delete_blobs(blobs)
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to remove virtual bucket (folder) {bucket}")
|
||||||
|
|
||||||
|
def copy(self, src_bucket, src_path, dest_bucket, dest_path):
|
||||||
|
# RENAMED PARAMETERS to match original interface
|
||||||
|
try:
|
||||||
|
bucket_obj = self.client.bucket(self.bucket_name)
|
||||||
|
|
||||||
|
src_blob_path = self._get_blob_path(src_bucket, src_path)
|
||||||
|
dest_blob_path = self._get_blob_path(dest_bucket, dest_path)
|
||||||
|
|
||||||
|
src_blob = bucket_obj.blob(src_blob_path)
|
||||||
|
|
||||||
|
if not src_blob.exists():
|
||||||
|
logging.error(f"Source object not found: {src_blob_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
bucket_obj.copy_blob(src_blob, bucket_obj, dest_blob_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except NotFound:
|
||||||
|
logging.error(f"Copy failed: Main bucket {self.bucket_name} does not exist.")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to copy {src_bucket}/{src_path} -> {dest_bucket}/{dest_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def move(self, src_bucket, src_path, dest_bucket, dest_path):
|
||||||
|
try:
|
||||||
|
if self.copy(src_bucket, src_path, dest_bucket, dest_path):
|
||||||
|
self.rm(src_bucket, src_path)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logging.error(f"Copy failed, move aborted: {src_bucket}/{src_path}")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Fail to move {src_bucket}/{src_path} -> {dest_bucket}/{dest_path}")
|
||||||
|
return False
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import './css/cloud9_night.less';
|
import './css/cloud9_night.less';
|
||||||
import './css/index.less';
|
import './css/index.less';
|
||||||
import { JsonEditorOptions, JsonEditorProps } from './interface';
|
import { JsonEditorOptions, JsonEditorProps } from './interface';
|
||||||
|
|
||||||
const defaultConfig: JsonEditorOptions = {
|
const defaultConfig: JsonEditorOptions = {
|
||||||
mode: 'code',
|
mode: 'code',
|
||||||
modes: ['tree', 'code'],
|
modes: ['tree', 'code'],
|
||||||
|
|
@ -14,6 +15,7 @@ const defaultConfig: JsonEditorOptions = {
|
||||||
enableTransform: false,
|
enableTransform: false,
|
||||||
indentation: 2,
|
indentation: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const JsonEditor: React.FC<JsonEditorProps> = ({
|
const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -25,43 +27,62 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const currentLanguageRef = useRef<string>(i18n.language);
|
const currentLanguageRef = useRef<string>(i18n.language);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
let isMounted = true;
|
||||||
const JSONEditor = require('jsoneditor');
|
|
||||||
import('jsoneditor/dist/jsoneditor.min.css');
|
|
||||||
|
|
||||||
if (containerRef.current) {
|
const initEditor = async () => {
|
||||||
// Default configuration options
|
if (typeof window !== 'undefined') {
|
||||||
const defaultOptions: JsonEditorOptions = {
|
try {
|
||||||
...defaultConfig,
|
const JSONEditorModule = await import('jsoneditor');
|
||||||
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
const JSONEditor = JSONEditorModule.default || JSONEditorModule;
|
||||||
onChange: () => {
|
|
||||||
if (editorRef.current && onChange) {
|
await import('jsoneditor/dist/jsoneditor.min.css');
|
||||||
try {
|
|
||||||
const updatedJson = editorRef.current.get();
|
if (isMounted && containerRef.current) {
|
||||||
onChange(updatedJson);
|
// Default configuration options
|
||||||
} catch (err) {
|
const defaultOptions: JsonEditorOptions = {
|
||||||
// Do not trigger onChange when parsing error occurs
|
...defaultConfig,
|
||||||
console.error(err);
|
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
||||||
}
|
onChange: () => {
|
||||||
|
if (editorRef.current && onChange) {
|
||||||
|
try {
|
||||||
|
const updatedJson = editorRef.current.get();
|
||||||
|
onChange(updatedJson);
|
||||||
|
} catch (err) {
|
||||||
|
// Do not trigger onChange when parsing error occurs
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...options, // Merge user provided options with defaults
|
||||||
|
};
|
||||||
|
|
||||||
|
editorRef.current = new JSONEditor(
|
||||||
|
containerRef.current,
|
||||||
|
defaultOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
editorRef.current.set(value);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
...options, // Merge user provided options with defaults
|
|
||||||
};
|
|
||||||
|
|
||||||
editorRef.current = new JSONEditor(
|
setIsLoading(false);
|
||||||
containerRef.current,
|
}
|
||||||
defaultOptions,
|
} catch (error) {
|
||||||
);
|
console.error('Failed to load jsoneditor:', error);
|
||||||
|
if (isMounted) {
|
||||||
if (value) {
|
setIsLoading(false);
|
||||||
editorRef.current.set(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
initEditor();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
if (typeof editorRef.current.destroy === 'function') {
|
if (typeof editorRef.current.destroy === 'function') {
|
||||||
editorRef.current.destroy();
|
editorRef.current.destroy();
|
||||||
|
|
@ -92,26 +113,38 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recreate the editor with new language
|
// Recreate the editor with new language
|
||||||
const JSONEditor = require('jsoneditor');
|
const initEditorWithNewLanguage = async () => {
|
||||||
|
try {
|
||||||
|
const JSONEditorModule = await import('jsoneditor');
|
||||||
|
const JSONEditor = JSONEditorModule.default || JSONEditorModule;
|
||||||
|
|
||||||
const newOptions: JsonEditorOptions = {
|
const newOptions: JsonEditorOptions = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
language: i18n.language === 'zh' ? 'zh-CN' : 'en',
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
if (editorRef.current && onChange) {
|
if (editorRef.current && onChange) {
|
||||||
try {
|
try {
|
||||||
const updatedJson = editorRef.current.get();
|
const updatedJson = editorRef.current.get();
|
||||||
onChange(updatedJson);
|
onChange(updatedJson);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Do not trigger onChange when parsing error occurs
|
// Do not trigger onChange when parsing error occurs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...options, // Merge user provided options with defaults
|
...options, // Merge user provided options with defaults
|
||||||
|
};
|
||||||
|
|
||||||
|
editorRef.current = new JSONEditor(containerRef.current, newOptions);
|
||||||
|
editorRef.current.set(currentData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'Failed to reload jsoneditor with new language:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editorRef.current = new JSONEditor(containerRef.current, newOptions);
|
initEditorWithNewLanguage();
|
||||||
editorRef.current.set(currentData);
|
|
||||||
}
|
}
|
||||||
}, [i18n.language, value, onChange, options]);
|
}, [i18n.language, value, onChange, options]);
|
||||||
|
|
||||||
|
|
@ -135,7 +168,13 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{ height }}
|
style={{ height }}
|
||||||
className={`ace-tomorrow-night w-full border border-border-button rounded-lg overflow-hidden bg-bg-input ${className} `}
|
className={`ace-tomorrow-night w-full border border-border-button rounded-lg overflow-hidden bg-bg-input ${className} `}
|
||||||
/>
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="text-text-secondary">Loading editor...</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
6
web/src/custom.d.ts
vendored
6
web/src/custom.d.ts
vendored
|
|
@ -2,3 +2,9 @@ declare module '*.md' {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'jsoneditor' {
|
||||||
|
const JSONEditor: any;
|
||||||
|
export default JSONEditor;
|
||||||
|
export = JSONEditor;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import { useDropdownManager } from './context';
|
||||||
|
|
||||||
import { AgentBackground } from '@/components/canvas/background';
|
import { AgentBackground } from '@/components/canvas/background';
|
||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
|
import { useNodeLoading } from '../hooks/use-node-loading';
|
||||||
import {
|
import {
|
||||||
useHideFormSheetOnNodeDeletion,
|
useHideFormSheetOnNodeDeletion,
|
||||||
useShowDrawer,
|
useShowDrawer,
|
||||||
|
|
@ -166,6 +167,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
});
|
});
|
||||||
const [lastSendLoading, setLastSendLoading] = useState(false);
|
const [lastSendLoading, setLastSendLoading] = useState(false);
|
||||||
|
|
||||||
|
const [currentSendLoading, setCurrentSendLoading] = useState(false);
|
||||||
|
|
||||||
const { handleBeforeDelete } = useBeforeDelete();
|
const { handleBeforeDelete } = useBeforeDelete();
|
||||||
|
|
||||||
const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance);
|
const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance);
|
||||||
|
|
@ -182,6 +185,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
}, [chatVisible, clearEventList, currentTaskId, stopMessage]);
|
}, [chatVisible, clearEventList, currentTaskId, stopMessage]);
|
||||||
|
|
||||||
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
|
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
|
||||||
|
setCurrentSendLoading(!!loading);
|
||||||
if (messageId === currentMessageId) {
|
if (messageId === currentMessageId) {
|
||||||
setLastSendLoading(loading);
|
setLastSendLoading(loading);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -249,7 +253,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
clearActiveDropdown,
|
clearActiveDropdown,
|
||||||
removePlaceholderNode,
|
removePlaceholderNode,
|
||||||
]);
|
]);
|
||||||
|
const { lastNode, setDerivedMessages, startButNotFinishedNodeIds } =
|
||||||
|
useNodeLoading({
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className={cn(styles.canvasWrapper, 'px-5 pb-5')}>
|
<div className={cn(styles.canvasWrapper, 'px-5 pb-5')}>
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -285,7 +292,15 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<AgentInstanceContext.Provider value={{ addCanvasNode, showFormDrawer }}>
|
<AgentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
addCanvasNode,
|
||||||
|
showFormDrawer,
|
||||||
|
lastNode,
|
||||||
|
currentSendLoading,
|
||||||
|
startButNotFinishedNodeIds,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
connectionMode={ConnectionMode.Loose}
|
connectionMode={ConnectionMode.Loose}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|
@ -380,9 +395,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
></FormSheet>
|
></FormSheet>
|
||||||
</AgentInstanceContext.Provider>
|
</AgentInstanceContext.Provider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{chatVisible && (
|
{chatVisible && (
|
||||||
<AgentChatContext.Provider
|
<AgentChatContext.Provider
|
||||||
value={{ showLogSheet, setLastSendLoadingFunc }}
|
value={{ showLogSheet, setLastSendLoadingFunc, setDerivedMessages }}
|
||||||
>
|
>
|
||||||
<AgentChatLogContext.Provider
|
<AgentChatLogContext.Provider
|
||||||
value={{ addEventList, setCurrentMessageId }}
|
value={{ addEventList, setCurrentMessageId }}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function InnerAgentNode({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
{isHeadAgent && (
|
{isHeadAgent && (
|
||||||
<>
|
<>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ function InnerBeginNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function InnerCategorizeNode({
|
||||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export function ExitLoopNode({ id, data, selected }: NodeProps<BaseNode<any>>) {
|
||||||
showRun={false}
|
showRun={false}
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ function InnerFileNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||||
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
const inputs: Record<string, BeginQuery> = get(data, 'form.inputs', {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function InnerRagNode({
|
||||||
showRun={needsSingleStepDebugging(data.label)}
|
showRun={needsSingleStepDebugging(data.label)}
|
||||||
showCopy={showCopyIcon(data.label)}
|
showCopy={showCopyIcon(data.label)}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ function InnerMessageNode({ id, data, selected }: NodeProps<IMessageNode>) {
|
||||||
const messages: string[] = get(data, 'form.content', []);
|
const messages: string[] = get(data, 'form.content', []);
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader
|
<NodeHeader
|
||||||
id={id}
|
id={id}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { HTMLAttributes } from 'react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { HTMLAttributes, useContext } from 'react';
|
||||||
|
import { AgentInstanceContext } from '../../context';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & { selected?: boolean };
|
type IProps = HTMLAttributes<HTMLDivElement> & { selected?: boolean };
|
||||||
|
|
||||||
export function NodeWrapper({ children, className, selected }: IProps) {
|
export function NodeWrapper({ children, className, selected, id }: IProps) {
|
||||||
|
const { currentSendLoading, startButNotFinishedNodeIds = [] } =
|
||||||
|
useContext(AgentInstanceContext);
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -12,6 +16,13 @@ export function NodeWrapper({ children, className, selected }: IProps) {
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{id &&
|
||||||
|
startButNotFinishedNodeIds.indexOf(id as string) > -1 &&
|
||||||
|
currentSendLoading && (
|
||||||
|
<div className=" absolute right-0 left-0 top-0 flex items-start justify-end p-2">
|
||||||
|
<Loader size={12} className=" animate-spin" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ function ParserNode({
|
||||||
}: NodeProps<BaseNode<ParserFormSchemaType>>) {
|
}: NodeProps<BaseNode<ParserFormSchemaType>>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ function InnerRetrievalNode({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.Start}
|
id={NodeHandleId.Start}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ function InnerSplitterNode({
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
showRun={false}
|
showRun={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
||||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<LeftEndHandle></LeftEndHandle>
|
<LeftEndHandle></LeftEndHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
<section className="gap-2.5 flex flex-col">
|
<section className="gap-2.5 flex flex-col">
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ function TokenizerNode({
|
||||||
showRun={false}
|
showRun={false}
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
>
|
>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function InnerToolNode({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected} id={id}>
|
||||||
<Handle
|
<Handle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
type="target"
|
type="target"
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ import {
|
||||||
} from '@/hooks/use-agent-request';
|
} from '@/hooks/use-agent-request';
|
||||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback, useContext } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
|
import { AgentChatContext } from '../context';
|
||||||
import DebugContent from '../debug-content';
|
import DebugContent from '../debug-content';
|
||||||
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
import { useAwaitCompentData } from '../hooks/use-chat-logic';
|
||||||
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
||||||
|
|
@ -49,6 +50,9 @@ function AgentChatBox() {
|
||||||
canvasId: canvasId as string,
|
canvasId: canvasId as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { setDerivedMessages } = useContext(AgentChatContext);
|
||||||
|
setDerivedMessages?.(derivedMessages);
|
||||||
|
|
||||||
const isTaskMode = useIsTaskMode();
|
const isTaskMode = useIsTaskMode();
|
||||||
|
|
||||||
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { INodeEvent } from '@/hooks/use-send-message';
|
||||||
|
import { IMessage } from '@/interfaces/database/chat';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { HandleType, Position } from '@xyflow/react';
|
import { HandleType, Position } from '@xyflow/react';
|
||||||
import { createContext } from 'react';
|
import { Dispatch, SetStateAction, createContext } from 'react';
|
||||||
import { useAddNode } from './hooks/use-add-node';
|
import { useAddNode } from './hooks/use-add-node';
|
||||||
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
||||||
import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer';
|
import { useShowFormDrawer, useShowLogSheet } from './hooks/use-show-drawer';
|
||||||
|
|
@ -13,7 +15,11 @@ type AgentInstanceContextType = Pick<
|
||||||
ReturnType<typeof useAddNode>,
|
ReturnType<typeof useAddNode>,
|
||||||
'addCanvasNode'
|
'addCanvasNode'
|
||||||
> &
|
> &
|
||||||
Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'>;
|
Pick<ReturnType<typeof useShowFormDrawer>, 'showFormDrawer'> & {
|
||||||
|
lastNode: INodeEvent | null;
|
||||||
|
currentSendLoading: boolean;
|
||||||
|
startButNotFinishedNodeIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
||||||
{} as AgentInstanceContextType,
|
{} as AgentInstanceContextType,
|
||||||
|
|
@ -22,7 +28,10 @@ export const AgentInstanceContext = createContext<AgentInstanceContextType>(
|
||||||
type AgentChatContextType = Pick<
|
type AgentChatContextType = Pick<
|
||||||
ReturnType<typeof useShowLogSheet>,
|
ReturnType<typeof useShowLogSheet>,
|
||||||
'showLogSheet'
|
'showLogSheet'
|
||||||
> & { setLastSendLoadingFunc: (loading: boolean, messageId: string) => void };
|
> & {
|
||||||
|
setLastSendLoadingFunc: (loading: boolean, messageId: string) => void;
|
||||||
|
setDerivedMessages: Dispatch<SetStateAction<IMessage[] | undefined>>;
|
||||||
|
};
|
||||||
|
|
||||||
export const AgentChatContext = createContext<AgentChatContextType>(
|
export const AgentChatContext = createContext<AgentChatContextType>(
|
||||||
{} as AgentChatContextType,
|
{} as AgentChatContextType,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const FormSheet = ({
|
||||||
<Sheet open={visible} modal={false}>
|
<Sheet open={visible} modal={false}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
className={cn('top-20 p-0 flex flex-col pb-20', {
|
className={cn('top-20 p-0 flex flex-col pb-20', {
|
||||||
'right-[620px]': chatVisible,
|
'right-[clamp(0px,34%,620px)]': chatVisible,
|
||||||
})}
|
})}
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
88
web/src/pages/agent/hooks/use-node-loading.ts
Normal file
88
web/src/pages/agent/hooks/use-node-loading.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import {
|
||||||
|
INodeData,
|
||||||
|
INodeEvent,
|
||||||
|
MessageEventType,
|
||||||
|
} from '@/hooks/use-send-message';
|
||||||
|
import { IMessage } from '@/interfaces/database/chat';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export const useNodeLoading = ({
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
}: {
|
||||||
|
currentEventListWithoutMessageById: (messageId: string) => INodeEvent[];
|
||||||
|
}) => {
|
||||||
|
const [derivedMessages, setDerivedMessages] = useState<IMessage[]>();
|
||||||
|
|
||||||
|
const lastMessageId = useMemo(() => {
|
||||||
|
return derivedMessages?.[derivedMessages?.length - 1]?.id;
|
||||||
|
}, [derivedMessages]);
|
||||||
|
|
||||||
|
const currentEventListWithoutMessage = useMemo(() => {
|
||||||
|
if (!lastMessageId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return currentEventListWithoutMessageById(lastMessageId);
|
||||||
|
}, [currentEventListWithoutMessageById, lastMessageId]);
|
||||||
|
|
||||||
|
const startedNodeList = useMemo(() => {
|
||||||
|
const duplicateList = currentEventListWithoutMessage?.filter(
|
||||||
|
(x) => x.event === MessageEventType.NodeStarted,
|
||||||
|
) as INodeEvent[];
|
||||||
|
|
||||||
|
// Remove duplicate nodes
|
||||||
|
return duplicateList?.reduce<Array<INodeEvent>>((pre, cur) => {
|
||||||
|
if (pre.every((x) => x.data.component_id !== cur.data.component_id)) {
|
||||||
|
pre.push(cur);
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
}, [currentEventListWithoutMessage]);
|
||||||
|
|
||||||
|
const filterFinishedNodeList = useCallback(() => {
|
||||||
|
const nodeEventList = currentEventListWithoutMessage
|
||||||
|
.filter(
|
||||||
|
(x) => x.event === MessageEventType.NodeFinished,
|
||||||
|
// x.event === MessageEventType.NodeFinished &&
|
||||||
|
// (x.data as INodeData)?.component_id === componentId,
|
||||||
|
)
|
||||||
|
.map((x) => x.data);
|
||||||
|
|
||||||
|
return nodeEventList;
|
||||||
|
}, [currentEventListWithoutMessage]);
|
||||||
|
|
||||||
|
const lastNode = useMemo(() => {
|
||||||
|
if (!startedNodeList) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return startedNodeList[startedNodeList.length - 1];
|
||||||
|
}, [startedNodeList]);
|
||||||
|
|
||||||
|
const startNodeIds = useMemo(() => {
|
||||||
|
if (!startedNodeList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return startedNodeList.map((x) => x.data.component_id);
|
||||||
|
}, [startedNodeList]);
|
||||||
|
|
||||||
|
const finishNodeIds = useMemo(() => {
|
||||||
|
if (!lastNode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nodeDataList = filterFinishedNodeList();
|
||||||
|
const finishNodeIdsTemp = nodeDataList.map(
|
||||||
|
(x: INodeData) => x.component_id,
|
||||||
|
);
|
||||||
|
return Array.from(new Set(finishNodeIdsTemp));
|
||||||
|
}, [lastNode, filterFinishedNodeList]);
|
||||||
|
|
||||||
|
const startButNotFinishedNodeIds = useMemo(() => {
|
||||||
|
return startNodeIds.filter((x) => !finishNodeIds.includes(x));
|
||||||
|
}, [finishNodeIds, startNodeIds]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastNode,
|
||||||
|
startButNotFinishedNodeIds,
|
||||||
|
filterFinishedNodeList,
|
||||||
|
setDerivedMessages,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -26,7 +26,7 @@ export function LogSheet({
|
||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
className={cn('top-20 right-[620px]')}
|
className={cn('top-20 right-[clamp(0px,34%,620px)]')}
|
||||||
onInteractOutside={(e) => e.preventDefault()}
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue