mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 16:45:08 +08:00
Compare commits
9 Commits
fb0426419e
...
23062cb27a
| Author | SHA1 | Date | |
|---|---|---|---|
| 23062cb27a | |||
| 63c2f5b821 | |||
| 0a0bfc02a0 | |||
| f0c34d4454 | |||
| 7c719f8365 | |||
| 4fc9e42e74 | |||
| 35539092d0 | |||
| 581a54fbbb | |||
| 9ca86d801e |
@ -166,6 +166,17 @@ def create():
|
||||
if DocumentService.query(name=req["name"], kb_id=kb_id):
|
||||
return get_data_error_result(message="Duplicated document name in the same knowledgebase.")
|
||||
|
||||
kb_root_folder = FileService.get_kb_folder(kb.tenant_id)
|
||||
if not kb_root_folder:
|
||||
return get_data_error_result(message="Cannot find the root folder.")
|
||||
kb_folder = FileService.new_a_file_from_kb(
|
||||
kb.tenant_id,
|
||||
kb.name,
|
||||
kb_root_folder["id"],
|
||||
)
|
||||
if not kb_folder:
|
||||
return get_data_error_result(message="Cannot find the kb folder for this file.")
|
||||
|
||||
doc = DocumentService.insert(
|
||||
{
|
||||
"id": get_uuid(),
|
||||
@ -180,6 +191,9 @@ def create():
|
||||
"size": 0,
|
||||
}
|
||||
)
|
||||
|
||||
FileService.add_file_from_kb(doc.to_dict(), kb_folder["id"], kb.tenant_id)
|
||||
|
||||
return get_json_result(data=doc.to_json())
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
@ -81,7 +81,7 @@ def set_api_key():
|
||||
raise Exception(m)
|
||||
chat_passed = True
|
||||
except Exception as e:
|
||||
msg += f"\nFail to access model({llm.llm_name}) using this api key." + str(
|
||||
msg += f"\nFail to access model({llm.fid}/{llm.llm_name}) using this api key." + str(
|
||||
e)
|
||||
elif not rerank_passed and llm.model_type == LLMType.RERANK:
|
||||
assert factory in RerankModel, f"Re-rank model from {factory} is not supported yet."
|
||||
@ -94,7 +94,7 @@ def set_api_key():
|
||||
rerank_passed = True
|
||||
logging.debug(f'passed model rerank {llm.llm_name}')
|
||||
except Exception as e:
|
||||
msg += f"\nFail to access model({llm.llm_name}) using this api key." + str(
|
||||
msg += f"\nFail to access model({llm.fid}/{llm.llm_name}) using this api key." + str(
|
||||
e)
|
||||
if any([embd_passed, chat_passed, rerank_passed]):
|
||||
msg = ''
|
||||
@ -229,7 +229,7 @@ def add_llm():
|
||||
if not tc and m.find("**ERROR**:") >= 0:
|
||||
raise Exception(m)
|
||||
except Exception as e:
|
||||
msg += f"\nFail to access model({mdl_nm})." + str(
|
||||
msg += f"\nFail to access model({factory}/{mdl_nm})." + str(
|
||||
e)
|
||||
elif llm["model_type"] == LLMType.RERANK:
|
||||
assert factory in RerankModel, f"RE-rank model from {factory} is not supported yet."
|
||||
@ -243,9 +243,9 @@ def add_llm():
|
||||
if len(arr) == 0:
|
||||
raise Exception("Not known.")
|
||||
except KeyError:
|
||||
msg += f"{factory} dose not support this model({mdl_nm})"
|
||||
msg += f"{factory} dose not support this model({factory}/{mdl_nm})"
|
||||
except Exception as e:
|
||||
msg += f"\nFail to access model({mdl_nm})." + str(
|
||||
msg += f"\nFail to access model({factory}/{mdl_nm})." + str(
|
||||
e)
|
||||
elif llm["model_type"] == LLMType.IMAGE2TEXT.value:
|
||||
assert factory in CvModel, f"Image to text model from {factory} is not supported yet."
|
||||
@ -260,7 +260,7 @@ def add_llm():
|
||||
if not m and not tc:
|
||||
raise Exception(m)
|
||||
except Exception as e:
|
||||
msg += f"\nFail to access model({mdl_nm})." + str(e)
|
||||
msg += f"\nFail to access model({factory}/{mdl_nm})." + str(e)
|
||||
elif llm["model_type"] == LLMType.TTS:
|
||||
assert factory in TTSModel, f"TTS model from {factory} is not supported yet."
|
||||
mdl = TTSModel[factory](
|
||||
@ -270,7 +270,7 @@ def add_llm():
|
||||
for resp in mdl.tts("Hello~ Ragflower!"):
|
||||
pass
|
||||
except RuntimeError as e:
|
||||
msg += f"\nFail to access model({mdl_nm})." + str(e)
|
||||
msg += f"\nFail to access model({factory}/{mdl_nm})." + str(e)
|
||||
else:
|
||||
# TODO: check other type of models
|
||||
pass
|
||||
@ -358,8 +358,6 @@ def my_llms():
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
|
||||
|
||||
@manager.route('/list', methods=['GET']) # noqa: F821
|
||||
@login_required
|
||||
def list_app():
|
||||
|
||||
@ -62,6 +62,8 @@ MYSQL_DBNAME=rag_flow
|
||||
# The port used to expose the MySQL service to the host machine,
|
||||
# allowing EXTERNAL access to the MySQL database running inside the Docker container.
|
||||
MYSQL_PORT=5455
|
||||
# The maximum size of communication packets sent to the MySQL server
|
||||
MYSQL_MAX_PACKET=1073741824
|
||||
|
||||
# The hostname where the MinIO service is exposed
|
||||
MINIO_HOST=minio
|
||||
|
||||
@ -9,6 +9,7 @@ mysql:
|
||||
port: 3306
|
||||
max_connections: 900
|
||||
stale_timeout: 300
|
||||
max_allowed_packet: ${MYSQL_MAX_PACKET:-1073741824}
|
||||
minio:
|
||||
user: '${MINIO_USER:-rag_flow}'
|
||||
password: '${MINIO_PASSWORD:-infini_rag_flow}'
|
||||
|
||||
@ -1216,11 +1216,11 @@ class LmStudioChat(Base):
|
||||
class OpenAI_APIChat(Base):
|
||||
_FACTORY_NAME = ["VLLM", "OpenAI-API-Compatible"]
|
||||
|
||||
def __init__(self, key, model_name, base_url):
|
||||
def __init__(self, key, model_name, base_url, **kwargs):
|
||||
if not base_url:
|
||||
raise ValueError("url cannot be None")
|
||||
model_name = model_name.split("___")[0]
|
||||
super().__init__(key, model_name, base_url)
|
||||
super().__init__(key, model_name, base_url, **kwargs)
|
||||
|
||||
|
||||
class PPIOChat(Base):
|
||||
|
||||
@ -37,7 +37,12 @@ from rag.utils import num_tokens_from_string, truncate
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, key, model_name):
|
||||
def __init__(self, key, model_name, **kwargs):
|
||||
"""
|
||||
Constructor for abstract base class.
|
||||
Parameters are accepted for interface consistency but are not stored.
|
||||
Subclasses should implement their own initialization as needed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def encode(self, texts: list):
|
||||
@ -864,7 +869,7 @@ class VoyageEmbed(Base):
|
||||
class HuggingFaceEmbed(Base):
|
||||
_FACTORY_NAME = "HuggingFace"
|
||||
|
||||
def __init__(self, key, model_name, base_url=None):
|
||||
def __init__(self, key, model_name, base_url=None, **kwargs):
|
||||
if not model_name:
|
||||
raise ValueError("Model name cannot be None")
|
||||
self.key = key
|
||||
@ -946,4 +951,4 @@ class Ai302Embed(Base):
|
||||
def __init__(self, key, model_name, base_url="https://api.302.ai/v1/embeddings"):
|
||||
if not base_url:
|
||||
base_url = "https://api.302.ai/v1/embeddings"
|
||||
super().__init__(key, model_name, base_url)
|
||||
super().__init__(key, model_name, base_url)
|
||||
@ -33,7 +33,11 @@ from api.utils.log_utils import log_exception
|
||||
from rag.utils import num_tokens_from_string, truncate
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, key, model_name):
|
||||
def __init__(self, key, model_name, **kwargs):
|
||||
"""
|
||||
Abstract base class constructor.
|
||||
Parameters are not stored; initialization is left to subclasses.
|
||||
"""
|
||||
pass
|
||||
|
||||
def similarity(self, query: str, texts: list):
|
||||
@ -315,7 +319,7 @@ class NvidiaRerank(Base):
|
||||
class LmStudioRerank(Base):
|
||||
_FACTORY_NAME = "LM-Studio"
|
||||
|
||||
def __init__(self, key, model_name, base_url):
|
||||
def __init__(self, key, model_name, base_url, **kwargs):
|
||||
pass
|
||||
|
||||
def similarity(self, query: str, texts: list):
|
||||
@ -396,7 +400,7 @@ class CoHereRerank(Base):
|
||||
class TogetherAIRerank(Base):
|
||||
_FACTORY_NAME = "TogetherAI"
|
||||
|
||||
def __init__(self, key, model_name, base_url):
|
||||
def __init__(self, key, model_name, base_url, **kwargs):
|
||||
pass
|
||||
|
||||
def similarity(self, query: str, texts: list):
|
||||
|
||||
@ -28,7 +28,11 @@ from rag.utils import num_tokens_from_string
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, key, model_name):
|
||||
def __init__(self, key, model_name, **kwargs):
|
||||
"""
|
||||
Abstract base class constructor.
|
||||
Parameters are not stored; initialization is left to subclasses.
|
||||
"""
|
||||
pass
|
||||
|
||||
def transcription(self, audio, **kwargs):
|
||||
|
||||
@ -63,7 +63,11 @@ class ServeTTSRequest(BaseModel):
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, key, model_name, base_url):
|
||||
def __init__(self, key, model_name, base_url, **kwargs):
|
||||
"""
|
||||
Abstract base class constructor.
|
||||
Parameters are not stored; subclasses should handle their own initialization.
|
||||
"""
|
||||
pass
|
||||
|
||||
def tts(self, audio):
|
||||
|
||||
@ -611,6 +611,10 @@ def naive_merge_with_images(texts, images, chunk_token_num=128, delimiter="\n。
|
||||
if re.match(f"^{dels}$", sub_sec):
|
||||
continue
|
||||
add_chunk(sub_sec, image)
|
||||
|
||||
for img in images:
|
||||
if isinstance(img, Image.Image):
|
||||
img.close()
|
||||
|
||||
return cks, result_images
|
||||
|
||||
|
||||
@ -231,7 +231,7 @@ async def get_storage_binary(bucket, name):
|
||||
return await trio.to_thread.run_sync(lambda: STORAGE_IMPL.get(bucket, name))
|
||||
|
||||
|
||||
@timeout(60*40, 1)
|
||||
@timeout(60*80, 1)
|
||||
async def build_chunks(task, progress_callback):
|
||||
if task["size"] > DOC_MAXIMUM_SIZE:
|
||||
set_progress(task["id"], prog=-1, msg="File size exceeds( <= %dMb )" %
|
||||
|
||||
@ -23,7 +23,7 @@ SET GLOBAL max_allowed_packet={}
|
||||
def get_opendal_config():
|
||||
try:
|
||||
opendal_config = get_base_config('opendal', {})
|
||||
if opendal_config.get("scheme") == 'mysql':
|
||||
if opendal_config.get("scheme", "mysql") == 'mysql':
|
||||
mysql_config = get_base_config('mysql', {})
|
||||
max_packet = mysql_config.get("max_allowed_packet", 134217728)
|
||||
kwargs = {
|
||||
@ -33,7 +33,7 @@ def get_opendal_config():
|
||||
"user": mysql_config.get("user", "root"),
|
||||
"password": mysql_config.get("password", ""),
|
||||
"database": mysql_config.get("name", "test_open_dal"),
|
||||
"table": opendal_config.get("config").get("oss_table", "opendal_storage"),
|
||||
"table": opendal_config.get("config", {}).get("oss_table", "opendal_storage"),
|
||||
"max_allowed_packet": str(max_packet)
|
||||
}
|
||||
kwargs["connection_string"] = f"mysql://{kwargs['user']}:{quote_plus(kwargs['password'])}@{kwargs['host']}:{kwargs['port']}/{kwargs['database']}?max_allowed_packet={max_packet}"
|
||||
|
||||
@ -30,6 +30,7 @@ class RAGFlowS3:
|
||||
self.s3_config = settings.S3
|
||||
self.access_key = self.s3_config.get('access_key', None)
|
||||
self.secret_key = self.s3_config.get('secret_key', None)
|
||||
self.session_token = self.s3_config.get('session_token', None)
|
||||
self.region = self.s3_config.get('region', None)
|
||||
self.endpoint_url = self.s3_config.get('endpoint_url', None)
|
||||
self.signature_version = self.s3_config.get('signature_version', None)
|
||||
@ -73,31 +74,32 @@ class RAGFlowS3:
|
||||
s3_params = {
|
||||
'aws_access_key_id': self.access_key,
|
||||
'aws_secret_access_key': self.secret_key,
|
||||
'aws_session_token': self.session_token,
|
||||
}
|
||||
if self.region in self.s3_config:
|
||||
if self.region:
|
||||
s3_params['region_name'] = self.region
|
||||
if 'endpoint_url' in self.s3_config:
|
||||
if self.endpoint_url:
|
||||
s3_params['endpoint_url'] = self.endpoint_url
|
||||
if 'signature_version' in self.s3_config:
|
||||
config_kwargs['signature_version'] = self.signature_version
|
||||
if 'addressing_style' in self.s3_config:
|
||||
config_kwargs['addressing_style'] = self.addressing_style
|
||||
if self.signature_version:
|
||||
s3_params['signature_version'] = self.signature_version
|
||||
if self.addressing_style:
|
||||
s3_params['addressing_style'] = self.addressing_style
|
||||
if config_kwargs:
|
||||
s3_params['config'] = Config(**config_kwargs)
|
||||
|
||||
self.conn = boto3.client('s3', **s3_params)
|
||||
self.conn = [boto3.client('s3', **s3_params)]
|
||||
except Exception:
|
||||
logging.exception(f"Fail to connect at region {self.region} or endpoint {self.endpoint_url}")
|
||||
|
||||
def __close__(self):
|
||||
del self.conn
|
||||
del self.conn[0]
|
||||
self.conn = None
|
||||
|
||||
@use_default_bucket
|
||||
def bucket_exists(self, bucket):
|
||||
def bucket_exists(self, bucket, *args, **kwargs):
|
||||
try:
|
||||
logging.debug(f"head_bucket bucketname {bucket}")
|
||||
self.conn.head_bucket(Bucket=bucket)
|
||||
self.conn[0].head_bucket(Bucket=bucket)
|
||||
exists = True
|
||||
except ClientError:
|
||||
logging.exception(f"head_bucket error {bucket}")
|
||||
@ -109,10 +111,10 @@ class RAGFlowS3:
|
||||
fnm = "txtxtxtxt1"
|
||||
fnm, binary = f"{self.prefix_path}/{fnm}" if self.prefix_path else fnm, b"_t@@@1"
|
||||
if not self.bucket_exists(bucket):
|
||||
self.conn.create_bucket(Bucket=bucket)
|
||||
self.conn[0].create_bucket(Bucket=bucket)
|
||||
logging.debug(f"create bucket {bucket} ********")
|
||||
|
||||
r = self.conn.upload_fileobj(BytesIO(binary), bucket, fnm)
|
||||
r = self.conn[0].upload_fileobj(BytesIO(binary), bucket, fnm)
|
||||
return r
|
||||
|
||||
def get_properties(self, bucket, key):
|
||||
@ -123,14 +125,14 @@ class RAGFlowS3:
|
||||
|
||||
@use_prefix_path
|
||||
@use_default_bucket
|
||||
def put(self, bucket, fnm, binary, **kwargs):
|
||||
def put(self, bucket, fnm, binary, *args, **kwargs):
|
||||
logging.debug(f"bucket name {bucket}; filename :{fnm}:")
|
||||
for _ in range(1):
|
||||
try:
|
||||
if not self.bucket_exists(bucket):
|
||||
self.conn.create_bucket(Bucket=bucket)
|
||||
self.conn[0].create_bucket(Bucket=bucket)
|
||||
logging.info(f"create bucket {bucket} ********")
|
||||
r = self.conn.upload_fileobj(BytesIO(binary), bucket, fnm)
|
||||
r = self.conn[0].upload_fileobj(BytesIO(binary), bucket, fnm)
|
||||
|
||||
return r
|
||||
except Exception:
|
||||
@ -140,18 +142,18 @@ class RAGFlowS3:
|
||||
|
||||
@use_prefix_path
|
||||
@use_default_bucket
|
||||
def rm(self, bucket, fnm, **kwargs):
|
||||
def rm(self, bucket, fnm, *args, **kwargs):
|
||||
try:
|
||||
self.conn.delete_object(Bucket=bucket, Key=fnm)
|
||||
self.conn[0].delete_object(Bucket=bucket, Key=fnm)
|
||||
except Exception:
|
||||
logging.exception(f"Fail rm {bucket}/{fnm}")
|
||||
|
||||
@use_prefix_path
|
||||
@use_default_bucket
|
||||
def get(self, bucket, fnm, **kwargs):
|
||||
def get(self, bucket, fnm, *args, **kwargs):
|
||||
for _ in range(1):
|
||||
try:
|
||||
r = self.conn.get_object(Bucket=bucket, Key=fnm)
|
||||
r = self.conn[0].get_object(Bucket=bucket, Key=fnm)
|
||||
object_data = r['Body'].read()
|
||||
return object_data
|
||||
except Exception:
|
||||
@ -162,9 +164,9 @@ class RAGFlowS3:
|
||||
|
||||
@use_prefix_path
|
||||
@use_default_bucket
|
||||
def obj_exist(self, bucket, fnm, **kwargs):
|
||||
def obj_exist(self, bucket, fnm, *args, **kwargs):
|
||||
try:
|
||||
if self.conn.head_object(Bucket=bucket, Key=fnm):
|
||||
if self.conn[0].head_object(Bucket=bucket, Key=fnm):
|
||||
return True
|
||||
except ClientError as e:
|
||||
if e.response['Error']['Code'] == '404':
|
||||
@ -174,10 +176,10 @@ class RAGFlowS3:
|
||||
|
||||
@use_prefix_path
|
||||
@use_default_bucket
|
||||
def get_presigned_url(self, bucket, fnm, expires, **kwargs):
|
||||
def get_presigned_url(self, bucket, fnm, expires, *args, **kwargs):
|
||||
for _ in range(10):
|
||||
try:
|
||||
r = self.conn.generate_presigned_url('get_object',
|
||||
r = self.conn[0].generate_presigned_url('get_object',
|
||||
Params={'Bucket': bucket,
|
||||
'Key': fnm},
|
||||
ExpiresIn=expires)
|
||||
|
||||
@ -58,7 +58,7 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
||||
<HoverCardTrigger>
|
||||
<div
|
||||
key={tag}
|
||||
className="w-fit flex items-center justify-center gap-2 border-dashed border px-1 rounded-sm bg-background-card"
|
||||
className="w-fit flex items-center justify-center gap-2 border-dashed border px-1 rounded-sm bg-bg-card"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="max-w-80 overflow-hidden text-ellipsis">
|
||||
@ -90,7 +90,7 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="h-8 bg-background-card"
|
||||
className="h-8 bg-bg-card"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputConfirm}
|
||||
@ -103,7 +103,7 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
||||
) : (
|
||||
<Button
|
||||
variant="dashed"
|
||||
className="w-fit flex items-center justify-center gap-2 bg-background-card"
|
||||
className="w-fit flex items-center justify-center gap-2 bg-bg-card"
|
||||
onClick={showInput}
|
||||
style={tagPlusStyle}
|
||||
>
|
||||
|
||||
@ -226,7 +226,7 @@ function MessageItem({
|
||||
? styles.messageTextDark
|
||||
: styles.messageText]: isAssistant,
|
||||
[styles.messageUserText]: !isAssistant,
|
||||
'bg-background-card': !isAssistant,
|
||||
'bg-bg-card': !isAssistant,
|
||||
})}
|
||||
>
|
||||
{item.data ? (
|
||||
|
||||
@ -11,7 +11,7 @@ const badgeVariants = cva(
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
secondary:
|
||||
'border-transparent bg-background-card text-text-sub-title-invert hover:bg-secondary/80 rounded-md',
|
||||
'border-transparent bg-bg-card text-text-sub-title-invert hover:bg-secondary/80 rounded-md',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
outline: 'text-foreground',
|
||||
|
||||
@ -15,8 +15,7 @@ const buttonVariants = cva(
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-text-sub-title-invert bg-transparent hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-background-card text-secondary-foreground hover:bg-secondary/80',
|
||||
secondary: 'bg-bg-card text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
tertiary:
|
||||
@ -52,7 +51,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
'bg-background-card',
|
||||
'bg-bg-card',
|
||||
buttonVariants({ variant, size, className }),
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@ -8,10 +8,7 @@ const Card = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-lg bg-background-card text-card-foreground shadow-sm',
|
||||
className,
|
||||
)}
|
||||
className={cn('rounded-lg bg-bg-card shadow-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
102
web/src/components/ui/modal/modal-manage.tsx
Normal file
102
web/src/components/ui/modal/modal-manage.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Modal, ModalProps } from './modal';
|
||||
|
||||
type PortalModalProps = Omit<ModalProps, 'open' | 'onOpenChange'> & {
|
||||
visible: boolean;
|
||||
onVisibleChange: (visible: boolean) => void;
|
||||
container?: HTMLElement;
|
||||
children: ReactNode;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const PortalModal = ({
|
||||
visible,
|
||||
onVisibleChange,
|
||||
container,
|
||||
children,
|
||||
...restProps
|
||||
}: PortalModalProps) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
return () => setMounted(false);
|
||||
}, []);
|
||||
|
||||
if (!mounted || !visible) return null;
|
||||
console.log('PortalModal:', visible);
|
||||
return createPortal(
|
||||
<Modal open={visible} onOpenChange={onVisibleChange} {...restProps}>
|
||||
{children}
|
||||
</Modal>,
|
||||
container || document.body,
|
||||
);
|
||||
};
|
||||
|
||||
export const createPortalModal = () => {
|
||||
let container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
let currentProps: any = {};
|
||||
let isVisible = false;
|
||||
let root: ReturnType<typeof createRoot> | null = null;
|
||||
|
||||
root = createRoot(container);
|
||||
const destroy = () => {
|
||||
if (root && container) {
|
||||
root.unmount();
|
||||
if (container.parentNode) {
|
||||
container.parentNode.removeChild(container);
|
||||
}
|
||||
root = null;
|
||||
}
|
||||
isVisible = false;
|
||||
currentProps = {};
|
||||
};
|
||||
const render = () => {
|
||||
const { onVisibleChange, ...props } = currentProps;
|
||||
const modalParam = {
|
||||
visible: isVisible,
|
||||
|
||||
onVisibleChange: (visible: boolean) => {
|
||||
isVisible = visible;
|
||||
if (onVisibleChange) {
|
||||
onVisibleChange(visible);
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
render();
|
||||
}
|
||||
},
|
||||
...props,
|
||||
};
|
||||
root?.render(isVisible ? <PortalModal {...modalParam} /> : null);
|
||||
};
|
||||
|
||||
const show = (props: PortalModalProps) => {
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
if (!root) {
|
||||
root = createRoot(container);
|
||||
}
|
||||
currentProps = { ...currentProps, ...props };
|
||||
isVisible = true;
|
||||
render();
|
||||
};
|
||||
|
||||
const hide = () => {
|
||||
isVisible = false;
|
||||
render();
|
||||
};
|
||||
|
||||
const update = (props = {}) => {
|
||||
currentProps = { ...currentProps, ...props };
|
||||
render();
|
||||
};
|
||||
|
||||
return { show, hide, update, destroy };
|
||||
};
|
||||
@ -1,15 +1,19 @@
|
||||
// src/components/ui/modal.tsx
|
||||
import { cn } from '@/lib/utils';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Loader, X } from 'lucide-react';
|
||||
import { FC, ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createPortalModal } from './modal-manage';
|
||||
|
||||
interface ModalProps {
|
||||
export interface ModalProps {
|
||||
open: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
title?: ReactNode;
|
||||
titleClassName?: string;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
footerClassName?: string;
|
||||
showfooter?: boolean;
|
||||
className?: string;
|
||||
size?: 'small' | 'default' | 'large';
|
||||
@ -24,13 +28,19 @@ interface ModalProps {
|
||||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
export interface ModalType extends FC<ModalProps> {
|
||||
show: typeof modalIns.show;
|
||||
hide: typeof modalIns.hide;
|
||||
}
|
||||
|
||||
export const Modal: FC<ModalProps> = ({
|
||||
const Modal: ModalType = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
titleClassName,
|
||||
children,
|
||||
footer,
|
||||
footerClassName,
|
||||
showfooter = true,
|
||||
className = '',
|
||||
size = 'default',
|
||||
@ -74,6 +84,7 @@ export const Modal: FC<ModalProps> = ({
|
||||
}, [onOpenChange, onOk]);
|
||||
const handleChange = (open: boolean) => {
|
||||
onOpenChange?.(open);
|
||||
console.log('open', open, onOpenChange);
|
||||
if (open) {
|
||||
handleOk();
|
||||
}
|
||||
@ -113,7 +124,12 @@ export const Modal: FC<ModalProps> = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center justify-end border-t border-border px-6 py-4">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-end px-6 py-4',
|
||||
footerClassName,
|
||||
)}
|
||||
>
|
||||
{footerTemp}
|
||||
</div>
|
||||
);
|
||||
@ -126,6 +142,7 @@ export const Modal: FC<ModalProps> = ({
|
||||
handleCancel,
|
||||
handleOk,
|
||||
showfooter,
|
||||
footerClassName,
|
||||
]);
|
||||
return (
|
||||
<DialogPrimitive.Root open={open} onOpenChange={handleChange}>
|
||||
@ -139,11 +156,23 @@ export const Modal: FC<ModalProps> = ({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* title */}
|
||||
{title && (
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||
<DialogPrimitive.Title className="text-lg font-medium text-foreground">
|
||||
{title}
|
||||
</DialogPrimitive.Title>
|
||||
{(title || closable) && (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center px-6 py-4',
|
||||
{
|
||||
'justify-end': closable && !title,
|
||||
'justify-between': closable && title,
|
||||
'justify-start': !closable,
|
||||
},
|
||||
titleClassName,
|
||||
)}
|
||||
>
|
||||
{title && (
|
||||
<DialogPrimitive.Title className="text-lg font-medium text-foreground">
|
||||
{title}
|
||||
</DialogPrimitive.Title>
|
||||
)}
|
||||
{closable && (
|
||||
<DialogPrimitive.Close asChild>
|
||||
<button
|
||||
@ -156,13 +185,9 @@ export const Modal: FC<ModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* title */}
|
||||
{!title && (
|
||||
<DialogPrimitive.Title className="text-lg font-medium text-foreground"></DialogPrimitive.Title>
|
||||
)}
|
||||
|
||||
{/* content */}
|
||||
<div className="p-6 overflow-y-auto max-h-[80vh] focus-visible:!outline-none">
|
||||
<div className="py-2 px-6 overflow-y-auto max-h-[80vh] focus-visible:!outline-none">
|
||||
{destroyOnClose && !open ? null : children}
|
||||
</div>
|
||||
|
||||
@ -175,43 +200,13 @@ export const Modal: FC<ModalProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
// example usage
|
||||
/*
|
||||
import { Modal } from '@/components/ui/modal';
|
||||
let modalIns = createPortalModal();
|
||||
Modal.show = modalIns
|
||||
? modalIns.show
|
||||
: () => {
|
||||
modalIns = createPortalModal();
|
||||
return modalIns.show;
|
||||
};
|
||||
Modal.hide = modalIns.hide;
|
||||
|
||||
function Demo() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setOpen(true)}>open modal</button>
|
||||
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title="title"
|
||||
footer={
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setOpen(false)} className="px-4 py-2 border rounded-md">
|
||||
cancel
|
||||
</button>
|
||||
<button onClick={() => setOpen(false)} className="px-4 py-2 bg-primary text-white rounded-md">
|
||||
ok
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="py-4">弹窗内容区域</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
title={'modal-title'}
|
||||
onOk={handleOk}
|
||||
confirmLoading={loading}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="py-4">弹窗内容区域</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
export { Modal };
|
||||
@ -8,7 +8,7 @@ const Table = React.forwardRef<
|
||||
>(({ className, rootClassName, ...props }, ref) => (
|
||||
<div
|
||||
className={cn(
|
||||
'relative w-full overflow-auto rounded-2xl bg-background-card',
|
||||
'relative w-full overflow-auto rounded-2xl bg-bg-card',
|
||||
rootClassName,
|
||||
)}
|
||||
>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
import { IConversation, IDialog } from '@/interfaces/database/chat';
|
||||
import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||
import { IClientConversation } from '@/pages/next-chats/chat/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||
import chatService from '@/services/next-chat-service ';
|
||||
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { has } from 'lodash';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
@ -17,6 +23,13 @@ export const enum ChatApiAction {
|
||||
RemoveDialog = 'removeDialog',
|
||||
SetDialog = 'setDialog',
|
||||
FetchDialog = 'fetchDialog',
|
||||
FetchConversationList = 'fetchConversationList',
|
||||
FetchConversation = 'fetchConversation',
|
||||
UpdateConversation = 'updateConversation',
|
||||
RemoveConversation = 'removeConversation',
|
||||
DeleteMessage = 'deleteMessage',
|
||||
FetchMindMap = 'fetchMindMap',
|
||||
FetchRelatedQuestions = 'fetchRelatedQuestions',
|
||||
}
|
||||
|
||||
export const useGetChatSearchParams = () => {
|
||||
@ -74,11 +87,17 @@ export const useFetchDialogList = () => {
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listDialog({
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
});
|
||||
const { data } = await chatService.listDialog(
|
||||
{
|
||||
params: {
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data ?? { dialogs: [], total: 0 };
|
||||
},
|
||||
@ -180,3 +199,227 @@ export const useFetchDialog = () => {
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
//#region Conversation
|
||||
|
||||
export const useClickConversationCard = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const handleClickConversation = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
return { handleClickConversation };
|
||||
};
|
||||
|
||||
export const useFetchConversationList = () => {
|
||||
const { id } = useParams();
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IConversation[]>({
|
||||
queryKey: [ChatApiAction.FetchConversationList, id],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listConversation(
|
||||
{ params: { dialog_id: id } },
|
||||
true,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
if (data.data.length > 0) {
|
||||
handleClickConversation(data.data[0].id, '');
|
||||
} else {
|
||||
handleClickConversation('', '');
|
||||
}
|
||||
}
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchConversation = () => {
|
||||
const { isNew, conversationId } = useGetChatSearchParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IClientConversation>({
|
||||
queryKey: [ChatApiAction.FetchConversation, conversationId],
|
||||
initialData: {} as IClientConversation,
|
||||
// enabled: isConversationIdExist(conversationId),
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
if (
|
||||
isNew !== 'true' &&
|
||||
isConversationIdExist(sharedId || conversationId)
|
||||
) {
|
||||
const { data } = await chatService.getConversation(
|
||||
{
|
||||
params: {
|
||||
conversationId: conversationId || sharedId,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const conversation = data?.data ?? {};
|
||||
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
|
||||
return { ...conversation, message: messageList };
|
||||
}
|
||||
return { message: [] };
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useUpdateConversation = () => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.UpdateConversation],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await chatService.setConversation({
|
||||
...params,
|
||||
conversation_id: params.conversation_id
|
||||
? params.conversation_id
|
||||
: getConversationId(),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ChatApiAction.FetchConversationList],
|
||||
});
|
||||
message.success(t(`message.modified`));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, updateConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRemoveConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.RemoveConversation],
|
||||
mutationFn: async (conversationIds: string[]) => {
|
||||
const { data } = await chatService.removeConversation({
|
||||
conversationIds,
|
||||
dialogId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ChatApiAction.FetchConversationList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteMessage = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.DeleteMessage],
|
||||
mutationFn: async (messageId: string) => {
|
||||
const { data } = await chatService.deleteMessage({
|
||||
messageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
if (data.code === 0) {
|
||||
message.success(t(`message.deleted`));
|
||||
}
|
||||
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteMessage: mutateAsync };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region search page
|
||||
|
||||
export const useFetchMindMap = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.FetchMindMap],
|
||||
gcTime: 0,
|
||||
mutationFn: async (params: IAskRequestBody) => {
|
||||
try {
|
||||
const ret = await chatService.getMindMap(params);
|
||||
return ret?.data?.data ?? {};
|
||||
} catch (error: any) {
|
||||
if (has(error, 'message')) {
|
||||
message.error(error.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchMindMap: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchRelatedQuestions = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.FetchRelatedQuestions],
|
||||
gcTime: 0,
|
||||
mutationFn: async (question: string): Promise<string[]> => {
|
||||
const { data } = await chatService.getRelatedQuestions({ question });
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchRelatedQuestions: mutateAsync };
|
||||
};
|
||||
//#endregion
|
||||
|
||||
@ -1028,7 +1028,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
'30d': '30 days',
|
||||
},
|
||||
publish: 'API',
|
||||
exeSQL: 'ExeSQL',
|
||||
exeSQL: 'Execute SQL',
|
||||
exeSQLDescription:
|
||||
'A component that performs SQL queries on a relational database, supporting querying from MySQL, PostgreSQL, or MariaDB.',
|
||||
dbType: 'Database Type',
|
||||
@ -1376,5 +1376,8 @@ This delimiter is used to split the input text into several text pieces echo of
|
||||
addMCP: 'Add MCP',
|
||||
editMCP: 'Edit MCP',
|
||||
},
|
||||
search: {
|
||||
createSearch: 'Create Search',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1310,4 +1310,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
},
|
||||
search: {
|
||||
createSearch: '新建查询',
|
||||
},
|
||||
};
|
||||
|
||||
@ -83,12 +83,12 @@ function InnerAgentNode({
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<section className="flex flex-col gap-2">
|
||||
<div className={'bg-background-card rounded-sm p-1'}>
|
||||
<div className={'bg-bg-card rounded-sm p-1'}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
{(isGotoMethod ||
|
||||
exceptionMethod === AgentExceptionMethod.Comment) && (
|
||||
<div className="bg-background-card rounded-sm p-1 flex justify-between gap-2">
|
||||
<div className="bg-bg-card rounded-sm p-1 flex justify-between gap-2">
|
||||
<span className="text-text-sub-title">On Failure</span>
|
||||
<span className="truncate flex-1 text-right">
|
||||
{t(`flow.${exceptionMethod}`)}
|
||||
|
||||
@ -31,13 +31,13 @@ export function InnerCategorizeNode({
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<div className={'bg-background-card rounded-sm px-1'}>
|
||||
<div className={'bg-bg-card rounded-sm px-1'}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
{positions.map((position) => {
|
||||
return (
|
||||
<div key={position.uuid}>
|
||||
<div className={'bg-background-card rounded-sm p-1 truncate'}>
|
||||
<div className={'bg-bg-card rounded-sm p-1 truncate'}>
|
||||
{position.name}
|
||||
</div>
|
||||
<CommonHandle
|
||||
|
||||
@ -42,7 +42,7 @@ function OperatorItemList({ operators }: OperatorItemProps) {
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
key={x}
|
||||
className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
className="hover:bg-bg-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
onClick={addCanvasNode(x, {
|
||||
nodeId,
|
||||
id,
|
||||
|
||||
@ -145,7 +145,7 @@ function EmbedDialog({
|
||||
{t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })}
|
||||
<span className="ml-1 inline-block">ID</span>
|
||||
</div>
|
||||
<div className="bg-background-card rounded-lg flex justify-between p-2">
|
||||
<div className="bg-bg-card rounded-lg flex justify-between p-2">
|
||||
<span>{token} </span>
|
||||
<CopyToClipboard text={token}></CopyToClipboard>
|
||||
</div>
|
||||
|
||||
@ -36,7 +36,7 @@ export function ToolCard({
|
||||
<li
|
||||
{...props}
|
||||
className={cn(
|
||||
'flex bg-background-card p-1 rounded-sm justify-between',
|
||||
'flex bg-bg-card p-1 rounded-sm justify-between',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@ -133,7 +133,7 @@ function ConditionCards({
|
||||
},
|
||||
)}
|
||||
>
|
||||
<section className="p-2 bg-background-card flex justify-between items-center">
|
||||
<section className="p-2 bg-bg-card flex justify-between items-center">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`${name}.${index}.cpn_id`}
|
||||
|
||||
@ -135,7 +135,7 @@ const ToolTimelineItem = ({
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="bg-background-card px-3"
|
||||
className="bg-bg-card px-3"
|
||||
>
|
||||
<AccordionItem value={idx.toString()}>
|
||||
<AccordionTrigger
|
||||
|
||||
@ -254,7 +254,7 @@ export const WorkFlowTimeline = ({
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="bg-background-card px-3"
|
||||
className="bg-bg-card px-3"
|
||||
>
|
||||
<AccordionItem value={idx.toString()}>
|
||||
<AccordionTrigger
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import MessageItem from '@/components/next-message-item';
|
||||
import { Modal } from '@/components/ui/modal';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { IAgentLogMessage } from '@/interfaces/database/agent';
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
import { Modal } from '@/components/ui/modal';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import Space from '@/components/ui/space';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
|
||||
@ -61,7 +61,7 @@ export default ({
|
||||
};
|
||||
return (
|
||||
<div className="flex pr-[25px]">
|
||||
<div className="flex items-center gap-4 bg-background-card text-muted w-fit h-[35px] rounded-md px-4 py-2 text-base">
|
||||
<div className="flex items-center gap-4 bg-bg-card text-muted-foreground w-fit h-[35px] rounded-md px-4 py-2">
|
||||
{textSelectOptions.map((option) => (
|
||||
<div
|
||||
key={option.value}
|
||||
@ -76,7 +76,7 @@ export default ({
|
||||
</div>
|
||||
<div className="ml-auto"></div>
|
||||
<Input
|
||||
className="bg-background-card text-muted-foreground"
|
||||
className="bg-bg-card text-muted-foreground"
|
||||
style={{ width: 200 }}
|
||||
placeholder={t('search')}
|
||||
icon={<SearchOutlined />}
|
||||
@ -86,7 +86,7 @@ export default ({
|
||||
<div className="w-[20px]"></div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button className="bg-background-card text-muted-foreground hover:bg-card">
|
||||
<Button className="bg-bg-card text-muted-foreground hover:bg-card">
|
||||
<ListFilter />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -95,10 +95,7 @@ export default ({
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="w-[20px]"></div>
|
||||
<Button
|
||||
onClick={() => createChunk()}
|
||||
className="bg-background-card text-primary"
|
||||
>
|
||||
<Button onClick={() => createChunk()} className="bg-bg-card text-primary">
|
||||
<Plus size={44} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -98,13 +98,13 @@ export default function DatasetSettings() {
|
||||
setCurrentTab(val);
|
||||
}}
|
||||
>
|
||||
<TabsList className="grid bg-background grid-cols-2 rounded-none bg-[#161618]">
|
||||
<TabsList className="grid bg-transparent grid-cols-2 rounded-none text-foreground">
|
||||
<TabsTrigger
|
||||
value="generalForm"
|
||||
className="group bg-transparent p-0 !border-transparent"
|
||||
>
|
||||
<div className="flex w-full h-full justify-center items-center bg-[#161618]">
|
||||
<span className="h-full group-data-[state=active]:border-b-2 border-white ">
|
||||
<div className="flex w-full h-full justify-center items-center">
|
||||
<span className="h-full group-data-[state=active]:border-b-2 border-foreground ">
|
||||
General
|
||||
</span>
|
||||
</div>
|
||||
@ -113,8 +113,8 @@ export default function DatasetSettings() {
|
||||
value="chunkMethodForm"
|
||||
className="group bg-transparent p-0 !border-transparent"
|
||||
>
|
||||
<div className="flex w-full h-full justify-center items-center bg-[#161618]">
|
||||
<span className="h-full group-data-[state=active]:border-b-2 border-white ">
|
||||
<div className="flex w-full h-full justify-center items-center">
|
||||
<span className="h-full group-data-[state=active]:border-b-2 border-foreground ">
|
||||
Chunk Method
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -84,7 +84,7 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
className={cn(
|
||||
'w-full justify-start gap-2.5 px-3 relative h-10 text-text-sub-title-invert',
|
||||
{
|
||||
'bg-background-card': active,
|
||||
'bg-bg-card': active,
|
||||
'text-text-title': active,
|
||||
},
|
||||
)}
|
||||
|
||||
@ -31,7 +31,7 @@ export function ChatCard({ data, showChatRenameModal }: IProps) {
|
||||
</section>
|
||||
<div className="flex justify-between items-end">
|
||||
<div className="w-full">
|
||||
<h3 className="text-lg font-semibold mb-2 line-clamp-1">
|
||||
<h3 className="text-lg font-semibold mb-2 line-clamp-1 truncate">
|
||||
{data.name}
|
||||
</h3>
|
||||
<p className="text-xs text-text-sub-title">{data.description}</p>
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ChatSettings } from './chat-settings';
|
||||
|
||||
export function ChatSettingSheet({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>{children}</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Chat Settings</SheetTitle>
|
||||
</SheetHeader>
|
||||
<ChatSettings></ChatSettings>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@ -7,8 +7,9 @@ import { ChatModelSettings } from './chat-model-settings';
|
||||
import { ChatPromptEngine } from './chat-prompt-engine';
|
||||
import { useChatSettingSchema } from './use-chat-setting-schema';
|
||||
|
||||
export function AppSettings() {
|
||||
export function ChatSettings() {
|
||||
const formSchema = useChatSettingSchema();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@ -32,27 +33,19 @@ export function AppSettings() {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-6 w-[500px] max-w-[25%] ">
|
||||
<div className="text-2xl font-bold mb-4 text-colors-text-neutral-strong px-6">
|
||||
App settings
|
||||
</div>
|
||||
<div className="overflow-auto max-h-[81vh] px-6 ">
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<ChatBasicSetting></ChatBasicSetting>
|
||||
<ChatPromptEngine></ChatPromptEngine>
|
||||
<ChatModelSettings></ChatModelSettings>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
<div className="p-6 text-center">
|
||||
<p className="text-colors-text-neutral-weak mb-1">
|
||||
There are unsaved changes
|
||||
</p>
|
||||
<Button variant={'tertiary'} className="w-full">
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
<section className="py-6">
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 overflow-auto max-h-[88vh] pr-4"
|
||||
>
|
||||
<ChatBasicSetting></ChatBasicSetting>
|
||||
<ChatPromptEngine></ChatPromptEngine>
|
||||
<ChatModelSettings></ChatModelSettings>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
<Button className="w-full my-4">Update</Button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -1,9 +1,95 @@
|
||||
import { ChatInput } from '@/components/chat-input';
|
||||
import { NextMessageInput } from '@/components/message-input/next';
|
||||
import MessageItem from '@/components/message-item';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useFetchDialog,
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||
import {
|
||||
useGetSendButtonDisabled,
|
||||
useSendButtonDisabled,
|
||||
} from '../hooks/use-button-disabled';
|
||||
import { useCreateConversationBeforeUploadDocument } from '../hooks/use-create-conversation';
|
||||
import { useSendMessage } from '../hooks/use-send-chat-message';
|
||||
import { buildMessageItemReference } from '../utils';
|
||||
|
||||
interface IProps {
|
||||
controller: AbortController;
|
||||
}
|
||||
|
||||
export function ChatBox({ controller }: IProps) {
|
||||
const {
|
||||
value,
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
sendLoading,
|
||||
derivedMessages,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
regenerateMessage,
|
||||
removeMessageById,
|
||||
stopOutputMessage,
|
||||
} = useSendMessage(controller);
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { data: currentDialog } = useFetchDialog();
|
||||
const { createConversationBeforeUploadDocument } =
|
||||
useCreateConversationBeforeUploadDocument();
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { data: conversation } = useFetchConversation();
|
||||
const disabled = useGetSendButtonDisabled();
|
||||
const sendDisabled = useSendButtonDisabled(value);
|
||||
|
||||
export function ChatBox() {
|
||||
return (
|
||||
<section className="border-x flex-1">
|
||||
<ChatInput></ChatInput>
|
||||
<section className="border-x flex flex-col p-5 w-full">
|
||||
<div ref={messageContainerRef} className="flex-1 overflow-auto">
|
||||
<div className="w-full">
|
||||
{derivedMessages?.map((message, i) => {
|
||||
return (
|
||||
<MessageItem
|
||||
loading={
|
||||
message.role === MessageType.Assistant &&
|
||||
sendLoading &&
|
||||
derivedMessages.length - 1 === i
|
||||
}
|
||||
key={buildMessageUuidWithRole(message)}
|
||||
item={message}
|
||||
nickname={userInfo.nickname}
|
||||
avatar={userInfo.avatar}
|
||||
avatarDialog={currentDialog.icon}
|
||||
reference={buildMessageItemReference(
|
||||
{
|
||||
message: derivedMessages,
|
||||
reference: conversation.reference,
|
||||
},
|
||||
message,
|
||||
)}
|
||||
// clickDocumentButton={clickDocumentButton}
|
||||
index={i}
|
||||
removeMessageById={removeMessageById}
|
||||
regenerateMessage={regenerateMessage}
|
||||
sendLoading={sendLoading}
|
||||
></MessageItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div ref={scrollRef} />
|
||||
</div>
|
||||
<NextMessageInput
|
||||
disabled={disabled}
|
||||
sendDisabled={sendDisabled}
|
||||
sendLoading={sendLoading}
|
||||
value={value}
|
||||
onInputChange={handleInputChange}
|
||||
onPressEnter={handlePressEnter}
|
||||
conversationId={conversationId}
|
||||
createConversationBeforeUploadDocument={
|
||||
createConversationBeforeUploadDocument
|
||||
}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,32 +1,48 @@
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchDialog } from '@/hooks/use-chat-request';
|
||||
import { EllipsisVertical } from 'lucide-react';
|
||||
import { AppSettings } from './app-settings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||
import { ChatBox } from './chat-box';
|
||||
import { Sessions } from './sessions';
|
||||
|
||||
export default function Chat() {
|
||||
const { navigateToChatList } = useNavigatePage();
|
||||
useFetchDialog();
|
||||
const { data } = useFetchDialog();
|
||||
const { t } = useTranslation();
|
||||
const { handleConversationCardClick, controller } =
|
||||
useHandleClickConversationCard();
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col">
|
||||
<PageHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<EllipsisVertical />
|
||||
</Button>
|
||||
<Button variant={'tertiary'} size={'sm'}>
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink onClick={navigateToChatList}>
|
||||
{t('chat.chat')}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{data.name}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</PageHeader>
|
||||
<div className="flex flex-1">
|
||||
<Sessions></Sessions>
|
||||
<ChatBox></ChatBox>
|
||||
<AppSettings></AppSettings>
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<Sessions
|
||||
handleConversationCardClick={handleConversationCardClick}
|
||||
></Sessions>
|
||||
<ChatBox controller={controller}></ChatBox>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
33
web/src/pages/next-chats/chat/interface.ts
Normal file
33
web/src/pages/next-chats/chat/interface.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { IConversation, IReference, Message } from '@/interfaces/database/chat';
|
||||
import { FormInstance } from 'antd';
|
||||
|
||||
export interface ISegmentedContentProps {
|
||||
show: boolean;
|
||||
form: FormInstance;
|
||||
setHasError: (hasError: boolean) => void;
|
||||
}
|
||||
|
||||
export interface IVariable {
|
||||
temperature: number;
|
||||
top_p: number;
|
||||
frequency_penalty: number;
|
||||
presence_penalty: number;
|
||||
max_tokens: number;
|
||||
}
|
||||
|
||||
export interface VariableTableDataType {
|
||||
key: string;
|
||||
variable: string;
|
||||
optional: boolean;
|
||||
}
|
||||
|
||||
export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;
|
||||
|
||||
export interface IMessage extends Message {
|
||||
id: string;
|
||||
reference?: IReference; // the latest news has reference
|
||||
}
|
||||
|
||||
export interface IClientConversation extends IConversation {
|
||||
message: IMessage[];
|
||||
}
|
||||
@ -1,38 +1,60 @@
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { EllipsisVertical, Plus } from 'lucide-react';
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
|
||||
import { ChatSettingSheet } from './app-settings/chat-settings-sheet';
|
||||
|
||||
function SessionCard() {
|
||||
return (
|
||||
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
|
||||
<CardContent className="px-3 py-2 flex justify-between items-center">
|
||||
xxx
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<EllipsisVertical />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
type SessionProps = Pick<
|
||||
ReturnType<typeof useHandleClickConversationCard>,
|
||||
'handleConversationCardClick'
|
||||
>;
|
||||
export function Sessions({ handleConversationCardClick }: SessionProps) {
|
||||
const { list: conversationList, addTemporaryConversation } =
|
||||
useSelectDerivedConversationList();
|
||||
|
||||
const handleCardClick = useCallback(
|
||||
(conversationId: string, isNew: boolean) => () => {
|
||||
handleConversationCardClick(conversationId, isNew);
|
||||
},
|
||||
[handleConversationCardClick],
|
||||
);
|
||||
}
|
||||
|
||||
export function Sessions() {
|
||||
const sessionList = new Array(10).fill(1);
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
|
||||
return (
|
||||
<section className="p-6 w-[400px] max-w-[20%]">
|
||||
<section className="p-6 w-[400px] max-w-[20%] flex flex-col">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<span className="text-colors-text-neutral-strong text-2xl font-bold">
|
||||
Sessions
|
||||
</span>
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<span className="text-xl font-bold">Conversations</span>
|
||||
<Button variant={'ghost'} onClick={addTemporaryConversation}>
|
||||
<Plus></Plus>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{sessionList.map((x) => (
|
||||
<SessionCard key={x.id}></SessionCard>
|
||||
<div className="space-y-4 flex-1 overflow-auto">
|
||||
{conversationList.map((x) => (
|
||||
<Card
|
||||
key={x.id}
|
||||
onClick={handleCardClick(x.id, x.is_new)}
|
||||
className={cn('cursor-pointer bg-transparent', {
|
||||
'bg-bg-card': conversationId === x.id,
|
||||
})}
|
||||
>
|
||||
<CardContent className="px-3 py-2 flex justify-between items-center group">
|
||||
{x.name}
|
||||
<MoreButton></MoreButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<ChatSettingSheet>
|
||||
<Button className="w-full">Chat Settings</Button>
|
||||
</ChatSettingSheet>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
14
web/src/pages/next-chats/hooks/use-button-disabled.tsx
Normal file
14
web/src/pages/next-chats/hooks/use-button-disabled.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { trim } from 'lodash';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
export const useGetSendButtonDisabled = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { id: dialogId } = useParams();
|
||||
|
||||
return dialogId === '' || conversationId === '';
|
||||
};
|
||||
|
||||
export const useSendButtonDisabled = (value: string) => {
|
||||
return trim(value) === '';
|
||||
};
|
||||
20
web/src/pages/next-chats/hooks/use-click-card.ts
Normal file
20
web/src/pages/next-chats/hooks/use-click-card.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useClickConversationCard } from '@/hooks/use-chat-request';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export function useHandleClickConversationCard() {
|
||||
const [controller, setController] = useState(new AbortController());
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
|
||||
const handleConversationCardClick = useCallback(
|
||||
(conversationId: string, isNew: boolean) => {
|
||||
handleClickConversation(conversationId, isNew ? 'true' : '');
|
||||
setController((pre) => {
|
||||
pre.abort();
|
||||
return new AbortController();
|
||||
});
|
||||
},
|
||||
[handleClickConversation],
|
||||
);
|
||||
|
||||
return { controller, handleConversationCardClick };
|
||||
}
|
||||
29
web/src/pages/next-chats/hooks/use-create-conversation.ts
Normal file
29
web/src/pages/next-chats/hooks/use-create-conversation.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
useSetChatRouteParams,
|
||||
useSetConversation,
|
||||
} from './use-send-chat-message';
|
||||
|
||||
export const useCreateConversationBeforeUploadDocument = () => {
|
||||
const { setConversation } = useSetConversation();
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
const { getConversationIsNew } = useSetChatRouteParams();
|
||||
|
||||
const createConversationBeforeUploadDocument = useCallback(
|
||||
async (message: string) => {
|
||||
const isNew = getConversationIsNew();
|
||||
if (isNew === 'true') {
|
||||
const data = await setConversation(message, true);
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
[setConversation, getConversationIsNew],
|
||||
);
|
||||
|
||||
return {
|
||||
createConversationBeforeUploadDocument,
|
||||
dialogId,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,85 @@
|
||||
import { ChatSearchParams, MessageType } from '@/constants/chat';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useFetchConversationList,
|
||||
useFetchDialogList,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IConversation } from '@/interfaces/database/chat';
|
||||
import { getConversationId } from '@/utils/chat';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
|
||||
export const useFindPrologueFromDialogList = () => {
|
||||
const { id: dialogId } = useParams();
|
||||
const { data } = useFetchDialogList();
|
||||
|
||||
const prologue = useMemo(() => {
|
||||
return data.dialogs.find((x) => x.id === dialogId)?.prompt_config.prologue;
|
||||
}, [dialogId, data]);
|
||||
|
||||
return prologue;
|
||||
};
|
||||
|
||||
export const useSetNewConversationRouteParams = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const setNewConversationRouteParams = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
return { setNewConversationRouteParams };
|
||||
};
|
||||
|
||||
export const useSelectDerivedConversationList = () => {
|
||||
const { t } = useTranslate('chat');
|
||||
|
||||
const [list, setList] = useState<Array<IConversation>>([]);
|
||||
const { data: conversationList, loading } = useFetchConversationList();
|
||||
const { id: dialogId } = useParams();
|
||||
const { setNewConversationRouteParams } = useSetNewConversationRouteParams();
|
||||
const prologue = useFindPrologueFromDialogList();
|
||||
|
||||
const addTemporaryConversation = useCallback(() => {
|
||||
const conversationId = getConversationId();
|
||||
setList((pre) => {
|
||||
if (dialogId) {
|
||||
setNewConversationRouteParams(conversationId, 'true');
|
||||
const nextList = [
|
||||
{
|
||||
id: conversationId,
|
||||
name: t('newConversation'),
|
||||
dialog_id: dialogId,
|
||||
is_new: true,
|
||||
message: [
|
||||
{
|
||||
content: prologue,
|
||||
role: MessageType.Assistant,
|
||||
},
|
||||
],
|
||||
} as any,
|
||||
...conversationList,
|
||||
];
|
||||
return nextList;
|
||||
}
|
||||
|
||||
return pre;
|
||||
});
|
||||
}, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]);
|
||||
|
||||
// When you first enter the page, select the top conversation card
|
||||
|
||||
useEffect(() => {
|
||||
setList([...conversationList]);
|
||||
}, [conversationList]);
|
||||
|
||||
return { list, addTemporaryConversation, loading };
|
||||
};
|
||||
279
web/src/pages/next-chats/hooks/use-send-chat-message.ts
Normal file
279
web/src/pages/next-chats/hooks/use-send-chat-message.ts
Normal file
@ -0,0 +1,279 @@
|
||||
import { ChatSearchParams, MessageType } from '@/constants/chat';
|
||||
import {
|
||||
useHandleMessageInputChange,
|
||||
useRegenerateMessage,
|
||||
useSelectDerivedMessages,
|
||||
useSendMessageWithSse,
|
||||
} from '@/hooks/logic-hooks';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useGetChatSearchParams,
|
||||
useUpdateConversation,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { Message } from '@/interfaces/database/chat';
|
||||
import api from '@/utils/api';
|
||||
import { trim } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IMessage } from '../chat/interface';
|
||||
import { useFindPrologueFromDialogList } from './use-select-conversation-list';
|
||||
|
||||
export const useSetChatRouteParams = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const setConversationIsNew = useCallback(
|
||||
(value: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.isNew, value);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
const getConversationIsNew = useCallback(() => {
|
||||
return newQueryParameters.get(ChatSearchParams.isNew);
|
||||
}, [newQueryParameters]);
|
||||
|
||||
return { setConversationIsNew, getConversationIsNew };
|
||||
};
|
||||
|
||||
export const useSelectNextMessages = () => {
|
||||
const {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
setDerivedMessages,
|
||||
derivedMessages,
|
||||
addNewestAnswer,
|
||||
addNewestQuestion,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
} = useSelectDerivedMessages();
|
||||
const { data: conversation, loading } = useFetchConversation();
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { id: dialogId } = useParams();
|
||||
const prologue = useFindPrologueFromDialogList();
|
||||
|
||||
const addPrologue = useCallback(() => {
|
||||
if (dialogId !== '' && isNew === 'true') {
|
||||
const nextMessage = {
|
||||
role: MessageType.Assistant,
|
||||
content: prologue,
|
||||
id: uuid(),
|
||||
} as IMessage;
|
||||
|
||||
setDerivedMessages([nextMessage]);
|
||||
}
|
||||
}, [dialogId, isNew, prologue, setDerivedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
addPrologue();
|
||||
}, [addPrologue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
conversationId &&
|
||||
isNew !== 'true' &&
|
||||
conversation.message?.length > 0
|
||||
) {
|
||||
setDerivedMessages(conversation.message);
|
||||
}
|
||||
|
||||
if (!conversationId) {
|
||||
setDerivedMessages([]);
|
||||
}
|
||||
}, [conversation.message, conversationId, setDerivedMessages, isNew]);
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
loading,
|
||||
addNewestAnswer,
|
||||
addNewestQuestion,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSetConversation = () => {
|
||||
const { id: dialogId } = useParams();
|
||||
const { updateConversation } = useUpdateConversation();
|
||||
|
||||
const setConversation = useCallback(
|
||||
async (
|
||||
message: string,
|
||||
isNew: boolean = false,
|
||||
conversationId?: string,
|
||||
) => {
|
||||
const data = await updateConversation({
|
||||
dialog_id: dialogId,
|
||||
name: message,
|
||||
is_new: isNew,
|
||||
conversation_id: conversationId,
|
||||
message: [
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: message,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
[updateConversation, dialogId],
|
||||
);
|
||||
|
||||
return { setConversation };
|
||||
};
|
||||
|
||||
export const useSendMessage = (controller: AbortController) => {
|
||||
const { setConversation } = useSetConversation();
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
|
||||
const { send, answer, done } = useSendMessageWithSse(
|
||||
api.completeConversation,
|
||||
);
|
||||
const {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
loading,
|
||||
addNewestAnswer,
|
||||
addNewestQuestion,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
} = useSelectNextMessages();
|
||||
const { setConversationIsNew, getConversationIsNew } =
|
||||
useSetChatRouteParams();
|
||||
|
||||
const stopOutputMessage = useCallback(() => {
|
||||
controller.abort();
|
||||
}, [controller]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async ({
|
||||
message,
|
||||
currentConversationId,
|
||||
messages,
|
||||
}: {
|
||||
message: Message;
|
||||
currentConversationId?: string;
|
||||
messages?: Message[];
|
||||
}) => {
|
||||
const res = await send(
|
||||
{
|
||||
conversation_id: currentConversationId ?? conversationId,
|
||||
messages: [...(messages ?? derivedMessages ?? []), message],
|
||||
},
|
||||
controller,
|
||||
);
|
||||
|
||||
if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) {
|
||||
// cancel loading
|
||||
setValue(message.content);
|
||||
console.info('removeLatestMessage111');
|
||||
removeLatestMessage();
|
||||
}
|
||||
},
|
||||
[
|
||||
derivedMessages,
|
||||
conversationId,
|
||||
removeLatestMessage,
|
||||
setValue,
|
||||
send,
|
||||
controller,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (message: Message) => {
|
||||
const isNew = getConversationIsNew();
|
||||
if (isNew !== 'true') {
|
||||
sendMessage({ message });
|
||||
} else {
|
||||
const data = await setConversation(
|
||||
message.content,
|
||||
true,
|
||||
conversationId,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
setConversationIsNew('');
|
||||
const id = data.data.id;
|
||||
// currentConversationIdRef.current = id;
|
||||
sendMessage({
|
||||
message,
|
||||
currentConversationId: id,
|
||||
messages: data.data.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
setConversation,
|
||||
sendMessage,
|
||||
setConversationIsNew,
|
||||
getConversationIsNew,
|
||||
conversationId,
|
||||
],
|
||||
);
|
||||
|
||||
const { regenerateMessage } = useRegenerateMessage({
|
||||
removeMessagesAfterCurrentMessage,
|
||||
sendMessage,
|
||||
messages: derivedMessages,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// #1289
|
||||
if (answer.answer && conversationId && isNew !== 'true') {
|
||||
addNewestAnswer(answer);
|
||||
}
|
||||
}, [answer, addNewestAnswer, conversationId, isNew]);
|
||||
|
||||
const handlePressEnter = useCallback(
|
||||
(documentIds: string[]) => {
|
||||
if (trim(value) === '') return;
|
||||
const id = uuid();
|
||||
|
||||
addNewestQuestion({
|
||||
content: value,
|
||||
doc_ids: documentIds,
|
||||
id,
|
||||
role: MessageType.User,
|
||||
});
|
||||
if (done) {
|
||||
setValue('');
|
||||
handleSendMessage({
|
||||
id,
|
||||
content: value.trim(),
|
||||
role: MessageType.User,
|
||||
doc_ids: documentIds,
|
||||
});
|
||||
}
|
||||
},
|
||||
[addNewestQuestion, handleSendMessage, done, setValue, value],
|
||||
);
|
||||
|
||||
return {
|
||||
handlePressEnter,
|
||||
handleInputChange,
|
||||
value,
|
||||
setValue,
|
||||
regenerateMessage,
|
||||
sendLoading: !done,
|
||||
loading,
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
removeMessageById,
|
||||
stopOutputMessage,
|
||||
};
|
||||
};
|
||||
@ -11,7 +11,8 @@ import { ChatCard } from './chat-card';
|
||||
import { useRenameChat } from './hooks/use-rename-chat';
|
||||
|
||||
export default function ChatList() {
|
||||
const { data, setPagination, pagination } = useFetchDialogList();
|
||||
const { data, setPagination, pagination, handleInputChange, searchString } =
|
||||
useFetchDialogList();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
initialChatName,
|
||||
@ -36,7 +37,11 @@ export default function ChatList() {
|
||||
return (
|
||||
<section className="flex flex-col w-full flex-1">
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar title="Chat apps">
|
||||
<ListFilterBar
|
||||
title="Chat apps"
|
||||
onSearchChange={handleInputChange}
|
||||
searchString={searchString}
|
||||
>
|
||||
<Button onClick={handleShowCreateModal}>
|
||||
<Plus className="size-2.5" />
|
||||
{t('chat.createChat')}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Modal } from '@/components/ui/modal';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import DebugContent from '@/pages/agent/debug-content';
|
||||
import { buildBeginInputListFromObject } from '@/pages/agent/form/begin-form/utils';
|
||||
|
||||
@ -1,23 +1,73 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { SearchCard } from './search-card';
|
||||
|
||||
export default function SearchList() {
|
||||
const { data } = useFetchFlowList();
|
||||
|
||||
const { t } = useTranslate('search');
|
||||
const [searchName, setSearchName] = useState('');
|
||||
const handleSearchChange = (value: string) => {
|
||||
console.log(value);
|
||||
};
|
||||
return (
|
||||
<section>
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar title="Search apps">
|
||||
<Button variant={'tertiary'} size={'sm'}>
|
||||
<ListFilterBar
|
||||
icon={
|
||||
<div className="rounded-sm bg-emerald-400 bg-gradient-to-t from-emerald-400 via-emerald-400 to-emerald-200 p-1 size-6 flex justify-center items-center">
|
||||
<Search size={14} className="font-bold m-auto" />
|
||||
</div>
|
||||
}
|
||||
title="Search apps"
|
||||
showFilter={false}
|
||||
onSearchChange={(e) => handleSearchChange(e.target.value)}
|
||||
>
|
||||
<Button
|
||||
variant={'default'}
|
||||
onClick={() => {
|
||||
Modal.show({
|
||||
title: (
|
||||
<div className="rounded-sm bg-emerald-400 bg-gradient-to-t from-emerald-400 via-emerald-400 to-emerald-200 p-1 size-6 flex justify-center items-center">
|
||||
<Search size={14} className="font-bold m-auto" />
|
||||
</div>
|
||||
),
|
||||
titleClassName: 'border-none',
|
||||
footerClassName: 'border-none',
|
||||
visible: true,
|
||||
children: (
|
||||
<div>
|
||||
<div>{t('createSearch')}</div>
|
||||
<div>name:</div>
|
||||
<Input
|
||||
defaultValue={searchName}
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value, e);
|
||||
setSearchName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
onOk: () => {
|
||||
console.log('ok', searchName);
|
||||
},
|
||||
onVisibleChange: (e) => {
|
||||
Modal.hide();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
{t('createSearch')}
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</div>
|
||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8 max-h-[84vh] overflow-auto px-8">
|
||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
||||
{data.map((x) => {
|
||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||
})}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
@ -15,38 +16,40 @@ export function SearchCard({ data }: IProps) {
|
||||
|
||||
return (
|
||||
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-4 flex gap-2 items-start group">
|
||||
<div className="flex justify-between mb-4">
|
||||
{data.avatar ? (
|
||||
<div
|
||||
className="w-[70px] h-[70px] rounded-xl bg-cover"
|
||||
style={{ backgroundImage: `url(${data.avatar})` }}
|
||||
/>
|
||||
) : (
|
||||
<Avatar className="w-[70px] h-[70px]">
|
||||
<AvatarImage src="https://github.com/shadcn.png" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
<RAGFlowAvatar
|
||||
className="w-[70px] h-[70px]"
|
||||
avatar={data.avatar}
|
||||
name={data.title}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<section className="flex justify-between">
|
||||
<div className="text-[20px] font-bold size-7 leading-5">
|
||||
{data.title}
|
||||
</div>
|
||||
<MoreButton></MoreButton>
|
||||
</section>
|
||||
|
||||
<div>An app that does things An app that does things</div>
|
||||
<section className="flex justify-between">
|
||||
<div>
|
||||
Search app
|
||||
<p className="text-sm opacity-80">
|
||||
{formatPureDate(data.update_time)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-x-2 invisible group-hover:visible">
|
||||
<Button variant="icon" size="icon" onClick={navigateToSearch}>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="icon" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">{data.title}</h3>
|
||||
<p>An app that does things An app that does things</p>
|
||||
<section className="flex justify-between pt-3">
|
||||
<div>
|
||||
Search app
|
||||
<p className="text-sm opacity-80">
|
||||
{formatPureDate(data.update_time)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Button variant="icon" size="icon" onClick={navigateToSearch}>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="icon" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -58,6 +58,28 @@ module.exports = {
|
||||
'dot-green': 'var(--dot-green)',
|
||||
'dot-red': 'var(--dot-red)',
|
||||
|
||||
/* design colors */
|
||||
|
||||
'bg-base': 'var(--bg-base)',
|
||||
'bg-card': 'var(--bg-card)',
|
||||
'text-primary': 'var(--text-primary)',
|
||||
'text-disabled': 'var(--text-disabled)',
|
||||
'text-input-tip': 'var(--text-input-tip)',
|
||||
'border-default': 'var(--border-default)',
|
||||
'border-accent': 'var(--border-accent)',
|
||||
'border-button': 'var(--border-button)',
|
||||
'accent-primary': 'var(--accent-primary)',
|
||||
'bg-accent': 'var(--bg-accent)',
|
||||
'state--success': 'var(--state--success)',
|
||||
'state--warning': 'var(--state--warning)',
|
||||
'state--error': 'var(--state--error)',
|
||||
'team-group': 'var(--team-group)',
|
||||
'team-member': 'var(--team-member)',
|
||||
'team-department': 'var(--team-department)',
|
||||
'bg-group': 'var(--bg-group)',
|
||||
'bg-member': 'var(--bg-member)',
|
||||
'bg-department': 'var(--bg-department)',
|
||||
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
|
||||
@ -93,6 +93,38 @@
|
||||
--input-border: rgba(22, 22, 24, 0.2);
|
||||
--dot-green: rgba(59, 160, 92, 1);
|
||||
--dot-red: rgba(216, 73, 75, 1);
|
||||
|
||||
/* design colors */
|
||||
|
||||
--bg-base: #f6f6f7;
|
||||
/* card color , dividing line */
|
||||
--bg-card: rgba(0, 0, 0, 0.05);
|
||||
/* Button ,Body text, Input completed text */
|
||||
--text-primary: #161618;
|
||||
--text-secondary: #75787a;
|
||||
--text-disabled: #b2b5b7;
|
||||
/* input placeholder color */
|
||||
--text-input-tip: #b2b5b7;
|
||||
/* Input default color */
|
||||
--border-default: rgba(0, 0, 0, 0.2);
|
||||
/* Input accent color */
|
||||
--border-accent: #000000;
|
||||
--border-button: rgba(0, 0, 0, 0.1);
|
||||
/* Regulators, parsing, switches, variables */
|
||||
--accent-primary: #4ca4e7;
|
||||
/* Output Variables Box */
|
||||
--bg-accent: rgba(76, 164, 231, 0.05);
|
||||
|
||||
--state--success: #3ba05c;
|
||||
--state--warning: #faad14;
|
||||
--state-error: #d8494b;
|
||||
|
||||
--team-group: #5ab77e;
|
||||
--team-member: #5c96c8;
|
||||
--team-department: #8866d3;
|
||||
--bg-group: rgba(90, 183, 126, 0.1);
|
||||
--bg-member: rgba(92, 150, 200, 0.1);
|
||||
--bg-department: rgba(136, 102, 211, 0.1);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@ -207,6 +239,18 @@
|
||||
|
||||
--dot-green: rgba(59, 160, 92, 1);
|
||||
--dot-red: rgba(216, 73, 75, 1);
|
||||
|
||||
/* design colors */
|
||||
|
||||
--bg-base: #161618;
|
||||
--bg-card: rgba(255, 255, 255, 0.05);
|
||||
--text-primary: #f6f6f7;
|
||||
--text-secondary: #b2b5b7;
|
||||
--text-disabled: #75787a;
|
||||
--text-input-tip: #75787a;
|
||||
--border-default: rgba(255, 255, 255, 0.2);
|
||||
--border-accent: #ffffff;
|
||||
--border-button: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user