mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
Compare commits
3 Commits
fix/slider
...
cohere/cle
| Author | SHA1 | Date | |
|---|---|---|---|
| 3835ae9364 | |||
| 83136ee4e8 | |||
| 8e152a83bf |
4
.github/workflows/build-push.yml
vendored
4
.github/workflows/build-push.yml
vendored
@ -6,8 +6,8 @@ on:
|
||||
- "main"
|
||||
- "deploy/dev"
|
||||
- "deploy/enterprise"
|
||||
tags:
|
||||
- "*"
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: build-push-${{ github.head_ref || github.run_id }}
|
||||
|
||||
@ -26,6 +26,9 @@ ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||
# Refresh token expiration time in days
|
||||
REFRESH_TOKEN_EXPIRE_DAYS=30
|
||||
|
||||
# celery configuration
|
||||
CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1
|
||||
|
||||
# redis configuration
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
@ -47,9 +50,6 @@ REDIS_USE_CLUSTERS=false
|
||||
REDIS_CLUSTERS=
|
||||
REDIS_CLUSTERS_PASSWORD=
|
||||
|
||||
# celery configuration
|
||||
CELERY_BROKER_URL=redis://:difyai123456@localhost:${REDIS_PORT}/1
|
||||
|
||||
# PostgreSQL database configuration
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=difyai123456
|
||||
|
||||
@ -37,12 +37,6 @@ select = [
|
||||
"UP", # pyupgrade rules
|
||||
"W191", # tab-indentation
|
||||
"W605", # invalid-escape-sequence
|
||||
# security related linting rules
|
||||
# RCE proctection (sort of)
|
||||
"S102", # exec-builtin, disallow use of `exec`
|
||||
"S307", # suspicious-eval-usage, disallow use of `eval` and `ast.literal_eval`
|
||||
"S301", # suspicious-pickle-usage, disallow use of `pickle` and its wrappers.
|
||||
"S302", # suspicious-marshal-usage, disallow use of `marshal` module
|
||||
]
|
||||
|
||||
ignore = [
|
||||
|
||||
@ -43,8 +43,3 @@ class OpenGaussConfig(BaseSettings):
|
||||
description="Max connection of the OpenGauss database",
|
||||
default=5,
|
||||
)
|
||||
|
||||
OPENGAUSS_ENABLE_PQ: bool = Field(
|
||||
description="Enable openGauss PQ acceleration feature",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
||||
|
||||
CURRENT_VERSION: str = Field(
|
||||
description="Dify version",
|
||||
default="1.1.3",
|
||||
default="1.1.1",
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
||||
@ -50,15 +50,7 @@ class AppListApi(Resource):
|
||||
parser.add_argument(
|
||||
"mode",
|
||||
type=str,
|
||||
choices=[
|
||||
"completion",
|
||||
"chat",
|
||||
"advanced-chat",
|
||||
"workflow",
|
||||
"agent-chat",
|
||||
"channel",
|
||||
"all",
|
||||
],
|
||||
choices=["chat", "workflow", "agent-chat", "channel", "all"],
|
||||
default="all",
|
||||
location="args",
|
||||
required=False,
|
||||
@ -138,6 +130,7 @@ class AppApi(Resource):
|
||||
parser.add_argument("icon_type", type=str, location="json")
|
||||
parser.add_argument("icon", type=str, location="json")
|
||||
parser.add_argument("icon_background", type=str, location="json")
|
||||
parser.add_argument("max_active_requests", type=int, location="json")
|
||||
parser.add_argument("use_icon_as_answer_icon", type=bool, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@ -75,7 +75,6 @@ class FilePreviewApi(Resource):
|
||||
if args["as_attachment"]:
|
||||
encoded_filename = quote(upload_file.name)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
|
||||
response.headers["Content-Type"] = "application/octet-stream"
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ from controllers.console.wraps import setup_required
|
||||
from controllers.inner_api import api
|
||||
from controllers.inner_api.wraps import enterprise_inner_api_only
|
||||
from events.tenant_event import tenant_was_created
|
||||
from extensions.ext_database import db
|
||||
from models.account import Account
|
||||
from services.account_service import TenantService
|
||||
|
||||
@ -20,7 +19,7 @@ class EnterpriseWorkspace(Resource):
|
||||
parser.add_argument("owner_email", type=str, required=True, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
account = db.session.query(Account).filter_by(email=args["owner_email"]).first()
|
||||
account = Account.query.filter_by(email=args["owner_email"]).first()
|
||||
if account is None:
|
||||
return {"message": "owner account not found."}, 404
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from flask import request
|
||||
from flask_login import current_user # type: ignore
|
||||
from flask_restful import marshal, reqparse # type: ignore
|
||||
from werkzeug.exceptions import NotFound
|
||||
@ -14,20 +13,10 @@ from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
from core.model_manager import ModelManager
|
||||
from core.model_runtime.entities.model_entities import ModelType
|
||||
from extensions.ext_database import db
|
||||
from fields.segment_fields import child_chunk_fields, segment_fields
|
||||
from models.dataset import Dataset
|
||||
from fields.segment_fields import segment_fields
|
||||
from models.dataset import Dataset, DocumentSegment
|
||||
from services.dataset_service import DatasetService, DocumentService, SegmentService
|
||||
from services.entities.knowledge_entities.knowledge_entities import SegmentUpdateArgs
|
||||
from services.errors.chunk import (
|
||||
ChildChunkDeleteIndexError,
|
||||
ChildChunkIndexingError,
|
||||
)
|
||||
from services.errors.chunk import (
|
||||
ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError,
|
||||
)
|
||||
from services.errors.chunk import (
|
||||
ChildChunkIndexingError as ChildChunkIndexingServiceError,
|
||||
)
|
||||
|
||||
|
||||
class SegmentApi(DatasetApiResource):
|
||||
@ -81,12 +70,10 @@ class SegmentApi(DatasetApiResource):
|
||||
return {"error": "Segments is required"}, 400
|
||||
|
||||
def get(self, tenant_id, dataset_id, document_id):
|
||||
"""Get segments."""
|
||||
"""Create single segment."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
page = request.args.get("page", default=1, type=int)
|
||||
limit = request.args.get("limit", default=20, type=int)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
@ -120,23 +107,19 @@ class SegmentApi(DatasetApiResource):
|
||||
status_list = args["status"]
|
||||
keyword = args["keyword"]
|
||||
|
||||
segments, total = SegmentService.get_segments(
|
||||
document_id=document_id,
|
||||
tenant_id=current_user.current_tenant_id,
|
||||
status_list=args["status"],
|
||||
keyword=args["keyword"],
|
||||
query = DocumentSegment.query.filter(
|
||||
DocumentSegment.document_id == str(document_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||
)
|
||||
|
||||
response = {
|
||||
"data": marshal(segments, segment_fields),
|
||||
"doc_form": document.doc_form,
|
||||
"total": total,
|
||||
"has_more": len(segments) == limit,
|
||||
"limit": limit,
|
||||
"page": page,
|
||||
}
|
||||
if status_list:
|
||||
query = query.filter(DocumentSegment.status.in_(status_list))
|
||||
|
||||
return response, 200
|
||||
if keyword:
|
||||
query = query.where(DocumentSegment.content.ilike(f"%{keyword}%"))
|
||||
|
||||
total = query.count()
|
||||
segments = query.order_by(DocumentSegment.position).all()
|
||||
return {"data": marshal(segments, segment_fields), "doc_form": document.doc_form, "total": total}, 200
|
||||
|
||||
|
||||
class DatasetSegmentApi(DatasetApiResource):
|
||||
@ -155,8 +138,9 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
segment = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||
).first()
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
SegmentService.delete_segment(segment, document, dataset)
|
||||
@ -195,7 +179,9 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
segment = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||
).first()
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
@ -204,200 +190,12 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
parser.add_argument("segment", type=dict, required=False, nullable=True, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
updated_segment = SegmentService.update_segment(
|
||||
SegmentUpdateArgs(**args["segment"]), segment, document, dataset
|
||||
)
|
||||
return {"data": marshal(updated_segment, segment_fields), "doc_form": document.doc_form}, 200
|
||||
|
||||
|
||||
class ChildChunkApi(DatasetApiResource):
|
||||
"""Resource for child chunks."""
|
||||
|
||||
@cloud_edition_billing_resource_check("vector_space", "dataset")
|
||||
@cloud_edition_billing_knowledge_limit_check("add_segment", "dataset")
|
||||
def post(self, tenant_id, dataset_id, document_id, segment_id):
|
||||
"""Create child chunk."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
# check document
|
||||
document_id = str(document_id)
|
||||
document = DocumentService.get_document(dataset.id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
# check embedding model setting
|
||||
if dataset.indexing_technique == "high_quality":
|
||||
try:
|
||||
model_manager = ModelManager()
|
||||
model_manager.get_model_instance(
|
||||
tenant_id=current_user.current_tenant_id,
|
||||
provider=dataset.embedding_model_provider,
|
||||
model_type=ModelType.TEXT_EMBEDDING,
|
||||
model=dataset.embedding_model,
|
||||
)
|
||||
except LLMBadRequestError:
|
||||
raise ProviderNotInitializeError(
|
||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
|
||||
# validate args
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
child_chunk = SegmentService.create_child_chunk(args.get("content"), segment, document, dataset)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
|
||||
return {"data": marshal(child_chunk, child_chunk_fields)}, 200
|
||||
|
||||
def get(self, tenant_id, dataset_id, document_id, segment_id):
|
||||
"""Get child chunks."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
# check document
|
||||
document_id = str(document_id)
|
||||
document = DocumentService.get_document(dataset.id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("limit", type=int, default=20, location="args")
|
||||
parser.add_argument("keyword", type=str, default=None, location="args")
|
||||
parser.add_argument("page", type=int, default=1, location="args")
|
||||
args = parser.parse_args()
|
||||
|
||||
page = args["page"]
|
||||
limit = min(args["limit"], 100)
|
||||
keyword = args["keyword"]
|
||||
|
||||
child_chunks = SegmentService.get_child_chunks(segment_id, document_id, dataset_id, page, limit, keyword)
|
||||
|
||||
return {
|
||||
"data": marshal(child_chunks.items, child_chunk_fields),
|
||||
"total": child_chunks.total,
|
||||
"total_pages": child_chunks.pages,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
}, 200
|
||||
|
||||
|
||||
class DatasetChildChunkApi(DatasetApiResource):
|
||||
"""Resource for updating child chunks."""
|
||||
|
||||
@cloud_edition_billing_knowledge_limit_check("add_segment", "dataset")
|
||||
def delete(self, tenant_id, dataset_id, document_id, segment_id, child_chunk_id):
|
||||
"""Delete child chunk."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
# check document
|
||||
document_id = str(document_id)
|
||||
document = DocumentService.get_document(dataset.id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
# check child chunk
|
||||
child_chunk_id = str(child_chunk_id)
|
||||
child_chunk = SegmentService.get_child_chunk_by_id(
|
||||
child_chunk_id=child_chunk_id, tenant_id=current_user.current_tenant_id
|
||||
)
|
||||
if not child_chunk:
|
||||
raise NotFound("Child chunk not found.")
|
||||
|
||||
try:
|
||||
SegmentService.delete_child_chunk(child_chunk, dataset)
|
||||
except ChildChunkDeleteIndexServiceError as e:
|
||||
raise ChildChunkDeleteIndexError(str(e))
|
||||
|
||||
return {"result": "success"}, 200
|
||||
|
||||
@cloud_edition_billing_resource_check("vector_space", "dataset")
|
||||
@cloud_edition_billing_knowledge_limit_check("add_segment", "dataset")
|
||||
def patch(self, tenant_id, dataset_id, document_id, segment_id, child_chunk_id):
|
||||
"""Update child chunk."""
|
||||
# check dataset
|
||||
dataset_id = str(dataset_id)
|
||||
tenant_id = str(tenant_id)
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
# get document
|
||||
document = DocumentService.get_document(dataset_id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
# get segment
|
||||
segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id)
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
# get child chunk
|
||||
child_chunk = SegmentService.get_child_chunk_by_id(
|
||||
child_chunk_id=child_chunk_id, tenant_id=current_user.current_tenant_id
|
||||
)
|
||||
if not child_chunk:
|
||||
raise NotFound("Child chunk not found.")
|
||||
|
||||
# validate args
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("content", type=str, required=True, nullable=False, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
child_chunk = SegmentService.update_child_chunk(
|
||||
args.get("content"), child_chunk, segment, document, dataset
|
||||
)
|
||||
except ChildChunkIndexingServiceError as e:
|
||||
raise ChildChunkIndexingError(str(e))
|
||||
|
||||
return {"data": marshal(child_chunk, child_chunk_fields)}, 200
|
||||
SegmentService.segment_create_args_validate(args["segment"], document)
|
||||
segment = SegmentService.update_segment(SegmentUpdateArgs(**args["segment"]), segment, document, dataset)
|
||||
return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
|
||||
|
||||
|
||||
api.add_resource(SegmentApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments")
|
||||
api.add_resource(
|
||||
DatasetSegmentApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>"
|
||||
)
|
||||
api.add_resource(
|
||||
ChildChunkApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks"
|
||||
)
|
||||
api.add_resource(
|
||||
DatasetChildChunkApi,
|
||||
"/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks/<uuid:child_chunk_id>",
|
||||
)
|
||||
|
||||
@ -27,9 +27,6 @@ class RateLimit:
|
||||
|
||||
def __init__(self, client_id: str, max_active_requests: int):
|
||||
self.max_active_requests = max_active_requests
|
||||
# must be called after max_active_requests is set
|
||||
if self.disabled():
|
||||
return
|
||||
if hasattr(self, "initialized"):
|
||||
return
|
||||
self.initialized = True
|
||||
@ -40,8 +37,6 @@ class RateLimit:
|
||||
self.flush_cache(use_local_value=True)
|
||||
|
||||
def flush_cache(self, use_local_value=False):
|
||||
if self.disabled():
|
||||
return
|
||||
self.last_recalculate_time = time.time()
|
||||
# flush max active requests
|
||||
if use_local_value or not redis_client.exists(self.max_active_requests_key):
|
||||
@ -64,18 +59,18 @@ class RateLimit:
|
||||
redis_client.hdel(self.active_requests_key, *timeout_requests)
|
||||
|
||||
def enter(self, request_id: Optional[str] = None) -> str:
|
||||
if self.disabled():
|
||||
return RateLimit._UNLIMITED_REQUEST_ID
|
||||
if time.time() - self.last_recalculate_time > RateLimit._ACTIVE_REQUESTS_COUNT_FLUSH_INTERVAL:
|
||||
self.flush_cache()
|
||||
if self.max_active_requests <= 0:
|
||||
return RateLimit._UNLIMITED_REQUEST_ID
|
||||
if not request_id:
|
||||
request_id = RateLimit.gen_request_key()
|
||||
|
||||
active_requests_count = redis_client.hlen(self.active_requests_key)
|
||||
if active_requests_count >= self.max_active_requests:
|
||||
raise AppInvokeQuotaExceededError(
|
||||
f"Too many requests. Please try again later. The current maximum concurrent requests allowed "
|
||||
f"for {self.client_id} is {self.max_active_requests}."
|
||||
"Too many requests. Please try again later. The current maximum "
|
||||
"concurrent requests allowed is {}.".format(self.max_active_requests)
|
||||
)
|
||||
redis_client.hset(self.active_requests_key, request_id, str(time.time()))
|
||||
return request_id
|
||||
@ -85,9 +80,6 @@ class RateLimit:
|
||||
return
|
||||
redis_client.hdel(self.active_requests_key, request_id)
|
||||
|
||||
def disabled(self):
|
||||
return self.max_active_requests <= 0
|
||||
|
||||
@staticmethod
|
||||
def gen_request_key() -> str:
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@ -49,7 +49,6 @@ class FileAttribute(StrEnum):
|
||||
TRANSFER_METHOD = "transfer_method"
|
||||
URL = "url"
|
||||
EXTENSION = "extension"
|
||||
RELATED_ID = "related_id"
|
||||
|
||||
|
||||
class ArrayFileAttribute(StrEnum):
|
||||
|
||||
@ -34,8 +34,6 @@ def get_attr(*, file: File, attr: FileAttribute):
|
||||
return file.remote_url
|
||||
case FileAttribute.EXTENSION:
|
||||
return file.extension
|
||||
case FileAttribute.RELATED_ID:
|
||||
return file.related_id
|
||||
|
||||
|
||||
def to_prompt_message_content(
|
||||
|
||||
@ -33,6 +33,7 @@ from core.ops.entities.trace_entity import (
|
||||
)
|
||||
from core.ops.langfuse_trace.langfuse_trace import LangFuseDataTrace
|
||||
from core.ops.langsmith_trace.langsmith_trace import LangSmithDataTrace
|
||||
from core.ops.opik_trace.opik_trace import OpikDataTrace
|
||||
from core.ops.utils import get_message_data
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_storage import storage
|
||||
@ -40,13 +41,6 @@ from models.model import App, AppModelConfig, Conversation, Message, MessageFile
|
||||
from models.workflow import WorkflowAppLog, WorkflowRun
|
||||
from tasks.ops_trace_task import process_trace_tasks
|
||||
|
||||
|
||||
def build_opik_trace_instance(config: OpikConfig):
|
||||
from core.ops.opik_trace.opik_trace import OpikDataTrace
|
||||
|
||||
return OpikDataTrace(config)
|
||||
|
||||
|
||||
provider_config_map: dict[str, dict[str, Any]] = {
|
||||
TracingProviderEnum.LANGFUSE.value: {
|
||||
"config_class": LangfuseConfig,
|
||||
@ -64,7 +58,7 @@ provider_config_map: dict[str, dict[str, Any]] = {
|
||||
"config_class": OpikConfig,
|
||||
"secret_keys": ["api_key"],
|
||||
"other_keys": ["project", "url", "workspace"],
|
||||
"trace_instance": lambda config: build_opik_trace_instance(config),
|
||||
"trace_instance": OpikDataTrace,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -97,7 +97,6 @@ class RetrievalService:
|
||||
all_documents=all_documents,
|
||||
retrieval_method=retrieval_method,
|
||||
exceptions=exceptions,
|
||||
document_ids_filter=document_ids_filter,
|
||||
)
|
||||
)
|
||||
concurrent.futures.wait(futures, timeout=30, return_when=concurrent.futures.ALL_COMPLETED)
|
||||
@ -223,7 +222,6 @@ class RetrievalService:
|
||||
all_documents: list,
|
||||
retrieval_method: str,
|
||||
exceptions: list,
|
||||
document_ids_filter: Optional[list[str]] = None,
|
||||
):
|
||||
with flask_app.app_context():
|
||||
try:
|
||||
@ -233,9 +231,7 @@ class RetrievalService:
|
||||
|
||||
vector_processor = Vector(dataset=dataset)
|
||||
|
||||
documents = vector_processor.search_by_full_text(
|
||||
cls.escape_query_for_search(query), top_k=top_k, document_ids_filter=document_ids_filter
|
||||
)
|
||||
documents = vector_processor.search_by_full_text(cls.escape_query_for_search(query), top_k=top_k)
|
||||
if documents:
|
||||
if (
|
||||
reranking_model
|
||||
|
||||
@ -102,6 +102,8 @@ class LindormVectorStore(BaseVector):
|
||||
if response["errors"]:
|
||||
for item in response["items"]:
|
||||
print(f"{item['index']['status']}: {item['index']['error']['type']}")
|
||||
else:
|
||||
self.refresh()
|
||||
|
||||
def get_ids_by_metadata_field(self, key: str, value: str):
|
||||
query: dict[str, Any] = {
|
||||
@ -165,7 +167,7 @@ class LindormVectorStore(BaseVector):
|
||||
if not all(isinstance(x, float) for x in query_vector):
|
||||
raise ValueError("All elements in query_vector should be floats")
|
||||
|
||||
top_k = kwargs.get("top_k", 3)
|
||||
top_k = kwargs.get("top_k", 10)
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
filters = []
|
||||
if document_ids_filter:
|
||||
@ -208,7 +210,7 @@ class LindormVectorStore(BaseVector):
|
||||
must_not = kwargs.get("must_not")
|
||||
should = kwargs.get("should")
|
||||
minimum_should_match = kwargs.get("minimum_should_match", 0)
|
||||
top_k = kwargs.get("top_k", 3)
|
||||
top_k = kwargs.get("top_k", 10)
|
||||
filters = kwargs.get("filter", [])
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
if document_ids_filter:
|
||||
@ -293,7 +295,7 @@ class LindormVectorStore(BaseVector):
|
||||
|
||||
|
||||
def default_text_mapping(dimension: int, method_name: str, **kwargs: Any) -> dict:
|
||||
excludes_from_source = kwargs.get("excludes_from_source", False)
|
||||
excludes_from_source = kwargs.get("excludes_from_source")
|
||||
analyzer = kwargs.get("analyzer", "ik_max_word")
|
||||
text_field = kwargs.get("text_field", Field.CONTENT_KEY.value)
|
||||
engine = kwargs["engine"]
|
||||
@ -354,12 +356,12 @@ def default_text_mapping(dimension: int, method_name: str, **kwargs: Any) -> dic
|
||||
|
||||
if excludes_from_source:
|
||||
# e.g. {"excludes": ["vector_field"]}
|
||||
mapping["mappings"]["_source"] = {"excludes": [vector_field]}
|
||||
mapping["mappings"]["_source"] = {"excludes": excludes_from_source}
|
||||
|
||||
if using_ugc and method_name == "ivfpq":
|
||||
mapping["settings"]["index"]["knn_routing"] = True
|
||||
mapping["settings"]["index"]["knn.offline.construction"] = True
|
||||
elif (using_ugc and method_name == "hnsw") or (using_ugc and method_name == "flat"):
|
||||
elif using_ugc and method_name == "hnsw" or using_ugc and method_name == "flat":
|
||||
mapping["settings"]["index"]["knn_routing"] = True
|
||||
return mapping
|
||||
|
||||
@ -456,7 +458,7 @@ def default_vector_search_query(
|
||||
"query": {"knn": {vector_field: {"vector": query_vector, "k": k}}},
|
||||
}
|
||||
|
||||
if filters is not None and len(filters) > 0:
|
||||
if filters is not None:
|
||||
# when using filter, transform filter from List[Dict] to Dict as valid format
|
||||
filter_dict = {"bool": {"must": filters}} if len(filters) > 1 else filters[0]
|
||||
search_query["query"]["knn"][vector_field]["filter"] = filter_dict # filter should be Dict
|
||||
|
||||
@ -25,7 +25,6 @@ class OpenGaussConfig(BaseModel):
|
||||
database: str
|
||||
min_connection: int
|
||||
max_connection: int
|
||||
enable_pq: bool = False # Enable PQ acceleration
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@ -58,11 +57,6 @@ CREATE TABLE IF NOT EXISTS {table_name} (
|
||||
);
|
||||
"""
|
||||
|
||||
SQL_CREATE_INDEX_PQ = """
|
||||
CREATE INDEX IF NOT EXISTS embedding_{table_name}_pq_idx ON {table_name}
|
||||
USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64, enable_pq=on, pq_m={pq_m});
|
||||
"""
|
||||
|
||||
SQL_CREATE_INDEX = """
|
||||
CREATE INDEX IF NOT EXISTS embedding_cosine_{table_name}_idx ON {table_name}
|
||||
USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
|
||||
@ -74,7 +68,6 @@ class OpenGauss(BaseVector):
|
||||
super().__init__(collection_name)
|
||||
self.pool = self._create_connection_pool(config)
|
||||
self.table_name = f"embedding_{collection_name}"
|
||||
self.pq_enabled = config.enable_pq
|
||||
|
||||
def get_type(self) -> str:
|
||||
return VectorType.OPENGAUSS
|
||||
@ -104,26 +97,7 @@ class OpenGauss(BaseVector):
|
||||
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
dimension = len(embeddings[0])
|
||||
self._create_collection(dimension)
|
||||
self.add_texts(texts, embeddings)
|
||||
self._create_index(dimension)
|
||||
|
||||
def _create_index(self, dimension: int):
|
||||
index_cache_key = f"vector_index_{self._collection_name}"
|
||||
lock_name = f"{index_cache_key}_lock"
|
||||
with redis_client.lock(lock_name, timeout=60):
|
||||
index_exist_cache_key = f"vector_index_{self._collection_name}"
|
||||
if redis_client.get(index_exist_cache_key):
|
||||
return
|
||||
|
||||
with self._get_cursor() as cur:
|
||||
if dimension <= 2000:
|
||||
if self.pq_enabled:
|
||||
cur.execute(SQL_CREATE_INDEX_PQ.format(table_name=self.table_name, pq_m=int(dimension / 4)))
|
||||
cur.execute("SET hnsw_earlystop_threshold = 320")
|
||||
|
||||
if not self.pq_enabled:
|
||||
cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name))
|
||||
redis_client.set(index_exist_cache_key, 1, ex=3600)
|
||||
return self.add_texts(texts, embeddings)
|
||||
|
||||
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
values = []
|
||||
@ -237,6 +211,8 @@ class OpenGauss(BaseVector):
|
||||
|
||||
with self._get_cursor() as cur:
|
||||
cur.execute(SQL_CREATE_TABLE.format(table_name=self.table_name, dimension=dimension))
|
||||
if dimension <= 2000:
|
||||
cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name))
|
||||
redis_client.set(collection_exist_cache_key, 1, ex=3600)
|
||||
|
||||
|
||||
@ -260,6 +236,5 @@ class OpenGaussFactory(AbstractVectorFactory):
|
||||
database=dify_config.OPENGAUSS_DATABASE or "dify",
|
||||
min_connection=dify_config.OPENGAUSS_MIN_CONNECTION,
|
||||
max_connection=dify_config.OPENGAUSS_MAX_CONNECTION,
|
||||
enable_pq=dify_config.OPENGAUSS_ENABLE_PQ or False,
|
||||
),
|
||||
)
|
||||
|
||||
@ -177,7 +177,7 @@ class PGVector(BaseVector):
|
||||
where_clause = ""
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
where_clause = f" WHERE meta->>'document_id' in ({document_ids}) "
|
||||
where_clause = f" WHERE metadata->>'document_id' in ({document_ids}) "
|
||||
|
||||
with self._get_cursor() as cur:
|
||||
cur.execute(
|
||||
@ -205,7 +205,7 @@ class PGVector(BaseVector):
|
||||
where_clause = ""
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
where_clause = f" AND meta->>'document_id' in ({document_ids}) "
|
||||
where_clause = f" AND metadata->>'document_id' in ({document_ids}) "
|
||||
if self.pg_bigm:
|
||||
cur.execute("SET pg_bigm.similarity_limit TO 0.000001")
|
||||
cur.execute(
|
||||
|
||||
@ -610,11 +610,7 @@ class DatasetRetrieval:
|
||||
if dataset.indexing_technique == "economy":
|
||||
# use keyword table query
|
||||
documents = RetrievalService.retrieve(
|
||||
retrieval_method="keyword_search",
|
||||
dataset_id=dataset.id,
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
document_ids_filter=document_ids_filter,
|
||||
retrieval_method="keyword_search", dataset_id=dataset.id, query=query, top_k=top_k
|
||||
)
|
||||
if documents:
|
||||
all_documents.extend(documents)
|
||||
@ -900,10 +896,7 @@ class DatasetRetrieval:
|
||||
return str(inputs.get(key, f"{{{{{key}}}}}"))
|
||||
|
||||
pattern = re.compile(r"\{\{(\w+)\}\}")
|
||||
output = pattern.sub(replacer, text)
|
||||
if isinstance(output, str):
|
||||
output = re.sub(r"[\r\n\t]+", " ", output).strip()
|
||||
return output
|
||||
return pattern.sub(replacer, text)
|
||||
|
||||
def _automatic_metadata_filter_func(
|
||||
self, dataset_ids: list, query: str, tenant_id: str, user_id: str, metadata_model_config: ModelConfig
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping, Sequence
|
||||
@ -361,13 +360,8 @@ class KnowledgeRetrievalNode(LLMNode):
|
||||
if isinstance(expected_value, str):
|
||||
expected_value = self.graph_runtime_state.variable_pool.convert_template(
|
||||
expected_value
|
||||
).value[0]
|
||||
if expected_value.value_type == "number":
|
||||
expected_value = expected_value.value
|
||||
elif expected_value.value_type == "string":
|
||||
expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip()
|
||||
else:
|
||||
raise ValueError("Invalid expected metadata value type")
|
||||
).text
|
||||
|
||||
filters = self._process_metadata_filter_func(
|
||||
condition.comparison_operator, metadata_name, expected_value, filters
|
||||
)
|
||||
|
||||
@ -24,7 +24,6 @@ vector_setting_fields = {
|
||||
}
|
||||
|
||||
weighted_score_fields = {
|
||||
"weight_type": fields.String,
|
||||
"keyword_setting": fields.Nested(keyword_setting_fields),
|
||||
"vector_setting": fields.Nested(vector_setting_fields),
|
||||
}
|
||||
|
||||
@ -910,7 +910,7 @@ class Embedding(db.Model): # type: ignore[name-defined]
|
||||
self.embedding = pickle.dumps(embedding_data, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def get_embedding(self) -> list[float]:
|
||||
return cast(list[float], pickle.loads(self.embedding)) # noqa: S301
|
||||
return cast(list[float], pickle.loads(self.embedding))
|
||||
|
||||
|
||||
class DatasetCollectionBinding(db.Model): # type: ignore[name-defined]
|
||||
|
||||
@ -15,11 +15,11 @@ from services.feature_service import FeatureService
|
||||
|
||||
|
||||
@app.celery.task(queue="dataset")
|
||||
def mail_clean_document_notify_task():
|
||||
def send_document_clean_notify_task():
|
||||
"""
|
||||
Async Send document clean notify mail
|
||||
|
||||
Usage: mail_clean_document_notify_task.delay()
|
||||
Usage: send_document_clean_notify_task.delay()
|
||||
"""
|
||||
if not mail.is_inited():
|
||||
return
|
||||
|
||||
@ -9,6 +9,7 @@ from flask_sqlalchemy.pagination import Pagination
|
||||
from configs import dify_config
|
||||
from constants.model_template import default_app_templates
|
||||
from core.agent.entities import AgentToolEntity
|
||||
from core.app.features.rate_limiting import RateLimit
|
||||
from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
from core.model_manager import ModelManager
|
||||
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
|
||||
@ -36,13 +37,9 @@ class AppService:
|
||||
filters = [App.tenant_id == tenant_id, App.is_universal == False]
|
||||
|
||||
if args["mode"] == "workflow":
|
||||
filters.append(App.mode == AppMode.WORKFLOW.value)
|
||||
elif args["mode"] == "completion":
|
||||
filters.append(App.mode == AppMode.COMPLETION.value)
|
||||
filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))
|
||||
elif args["mode"] == "chat":
|
||||
filters.append(App.mode == AppMode.CHAT.value)
|
||||
elif args["mode"] == "advanced-chat":
|
||||
filters.append(App.mode == AppMode.ADVANCED_CHAT.value)
|
||||
filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value]))
|
||||
elif args["mode"] == "agent-chat":
|
||||
filters.append(App.mode == AppMode.AGENT_CHAT.value)
|
||||
elif args["mode"] == "channel":
|
||||
@ -225,6 +222,7 @@ class AppService:
|
||||
"""
|
||||
app.name = args.get("name")
|
||||
app.description = args.get("description", "")
|
||||
app.max_active_requests = args.get("max_active_requests")
|
||||
app.icon_type = args.get("icon_type", "emoji")
|
||||
app.icon = args.get("icon")
|
||||
app.icon_background = args.get("icon_background")
|
||||
@ -233,6 +231,9 @@ class AppService:
|
||||
app.updated_at = datetime.now(UTC).replace(tzinfo=None)
|
||||
db.session.commit()
|
||||
|
||||
if app.max_active_requests is not None:
|
||||
rate_limit = RateLimit(app.id, app.max_active_requests)
|
||||
rate_limit.flush_cache(use_local_value=True)
|
||||
return app
|
||||
|
||||
def update_app_name(self, app: App, name: str) -> App:
|
||||
|
||||
@ -150,13 +150,7 @@ class ClearFreePlanTenantExpiredLogs:
|
||||
while True:
|
||||
with Session(db.engine).no_autoflush as session:
|
||||
workflow_runs = (
|
||||
session.query(WorkflowRun)
|
||||
.filter(
|
||||
WorkflowRun.tenant_id == tenant_id,
|
||||
WorkflowRun.created_at < datetime.datetime.now() - datetime.timedelta(days=days),
|
||||
)
|
||||
.limit(batch)
|
||||
.all()
|
||||
session.query(WorkflowRun).filter(WorkflowRun.tenant_id == tenant_id).limit(batch).all()
|
||||
)
|
||||
|
||||
if len(workflow_runs) == 0:
|
||||
|
||||
@ -2140,88 +2140,6 @@ class SegmentService:
|
||||
query = query.where(ChildChunk.content.ilike(f"%{keyword}%"))
|
||||
return query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False)
|
||||
|
||||
@classmethod
|
||||
def get_child_chunk_by_id(cls, child_chunk_id: str, tenant_id: str) -> Optional[ChildChunk]:
|
||||
"""Get a child chunk by its ID."""
|
||||
result = ChildChunk.query.filter(ChildChunk.id == child_chunk_id, ChildChunk.tenant_id == tenant_id).first()
|
||||
return result if isinstance(result, ChildChunk) else None
|
||||
|
||||
@classmethod
|
||||
def get_segments(
|
||||
cls, document_id: str, tenant_id: str, status_list: list[str] | None = None, keyword: str | None = None
|
||||
):
|
||||
"""Get segments for a document with optional filtering."""
|
||||
query = DocumentSegment.query.filter(
|
||||
DocumentSegment.document_id == document_id, DocumentSegment.tenant_id == tenant_id
|
||||
)
|
||||
|
||||
if status_list:
|
||||
query = query.filter(DocumentSegment.status.in_(status_list))
|
||||
|
||||
if keyword:
|
||||
query = query.filter(DocumentSegment.content.ilike(f"%{keyword}%"))
|
||||
|
||||
segments = query.order_by(DocumentSegment.position.asc()).all()
|
||||
total = len(segments)
|
||||
|
||||
return segments, total
|
||||
|
||||
@classmethod
|
||||
def update_segment_by_id(
|
||||
cls, tenant_id: str, dataset_id: str, document_id: str, segment_id: str, segment_data: dict, user_id: str
|
||||
) -> tuple[DocumentSegment, Document]:
|
||||
"""Update a segment by its ID with validation and checks."""
|
||||
# check dataset
|
||||
dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
raise NotFound("Dataset not found.")
|
||||
|
||||
# check user's model setting
|
||||
DatasetService.check_dataset_model_setting(dataset)
|
||||
|
||||
# check document
|
||||
document = DocumentService.get_document(dataset_id, document_id)
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
|
||||
# check embedding model setting if high quality
|
||||
if dataset.indexing_technique == "high_quality":
|
||||
try:
|
||||
model_manager = ModelManager()
|
||||
model_manager.get_model_instance(
|
||||
tenant_id=user_id,
|
||||
provider=dataset.embedding_model_provider,
|
||||
model_type=ModelType.TEXT_EMBEDDING,
|
||||
model=dataset.embedding_model,
|
||||
)
|
||||
except LLMBadRequestError:
|
||||
raise ValueError(
|
||||
"No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
|
||||
)
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ValueError(ex.description)
|
||||
|
||||
# check segment
|
||||
segment = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == segment_id, DocumentSegment.tenant_id == user_id
|
||||
).first()
|
||||
if not segment:
|
||||
raise NotFound("Segment not found.")
|
||||
|
||||
# validate and update segment
|
||||
cls.segment_create_args_validate(segment_data, document)
|
||||
updated_segment = cls.update_segment(SegmentUpdateArgs(**segment_data), segment, document, dataset)
|
||||
|
||||
return updated_segment, document
|
||||
|
||||
@classmethod
|
||||
def get_segment_by_id(cls, segment_id: str, tenant_id: str) -> Optional[DocumentSegment]:
|
||||
"""Get a segment by its ID."""
|
||||
result = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == segment_id, DocumentSegment.tenant_id == tenant_id
|
||||
).first()
|
||||
return result if isinstance(result, DocumentSegment) else None
|
||||
|
||||
|
||||
class DatasetCollectionBindingService:
|
||||
@classmethod
|
||||
|
||||
@ -563,7 +563,6 @@ OPENGAUSS_PASSWORD=Dify@123
|
||||
OPENGAUSS_DATABASE=dify
|
||||
OPENGAUSS_MIN_CONNECTION=1
|
||||
OPENGAUSS_MAX_CONNECTION=5
|
||||
OPENGAUSS_ENABLE_PQ=false
|
||||
|
||||
# Upstash Vector configuration, only available when VECTOR_STORE is `upstash`
|
||||
UPSTASH_VECTOR_URL=https://xxx-vector.upstash.io
|
||||
|
||||
@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.1.3
|
||||
image: langgenius/dify-api:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -29,7 +29,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.1.3
|
||||
image: langgenius/dify-api:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -53,7 +53,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.1.3
|
||||
image: langgenius/dify-web:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@ -110,7 +110,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@ -43,7 +43,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@ -259,7 +259,6 @@ x-shared-env: &shared-api-worker-env
|
||||
OPENGAUSS_DATABASE: ${OPENGAUSS_DATABASE:-dify}
|
||||
OPENGAUSS_MIN_CONNECTION: ${OPENGAUSS_MIN_CONNECTION:-1}
|
||||
OPENGAUSS_MAX_CONNECTION: ${OPENGAUSS_MAX_CONNECTION:-5}
|
||||
OPENGAUSS_ENABLE_PQ: ${OPENGAUSS_ENABLE_PQ:-false}
|
||||
UPSTASH_VECTOR_URL: ${UPSTASH_VECTOR_URL:-https://xxx-vector.upstash.io}
|
||||
UPSTASH_VECTOR_TOKEN: ${UPSTASH_VECTOR_TOKEN:-dify}
|
||||
UPLOAD_FILE_SIZE_LIMIT: ${UPLOAD_FILE_SIZE_LIMIT:-15}
|
||||
@ -433,7 +432,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.1.3
|
||||
image: langgenius/dify-api:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -460,7 +459,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.1.3
|
||||
image: langgenius/dify-api:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@ -484,7 +483,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.1.3
|
||||
image: langgenius/dify-web:1.1.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@ -541,7 +540,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@ -3,7 +3,7 @@ import Main from '@/app/components/app/log-annotation'
|
||||
import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type'
|
||||
|
||||
export type IProps = {
|
||||
params: Promise<{ appId: string }>
|
||||
params: { appId: string }
|
||||
}
|
||||
|
||||
const Logs = async () => {
|
||||
|
||||
@ -3,16 +3,12 @@ import type { Locale } from '@/i18n'
|
||||
import DevelopMain from '@/app/components/develop'
|
||||
|
||||
export type IDevelopProps = {
|
||||
params: Promise<{ locale: Locale; appId: string }>
|
||||
params: { locale: Locale; appId: string }
|
||||
}
|
||||
|
||||
const Develop = async (props: IDevelopProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
appId,
|
||||
} = params
|
||||
|
||||
const Develop = async ({
|
||||
params: { appId },
|
||||
}: IDevelopProps) => {
|
||||
return <DevelopMain appId={appId} />
|
||||
}
|
||||
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useUnmount } from 'ahooks'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
RiDashboard2Fill,
|
||||
RiDashboard2Line,
|
||||
RiFileList3Fill,
|
||||
RiFileList3Line,
|
||||
RiTerminalBoxFill,
|
||||
RiTerminalBoxLine,
|
||||
RiTerminalWindowFill,
|
||||
RiTerminalWindowLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import type { NavIcon } from '@/app/components/app-sidebar/navLink'
|
||||
import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
|
||||
import AppContext, { useAppContext } from '@/context/app-context'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import type { App } from '@/types/app'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
appId: string
|
||||
}
|
||||
|
||||
const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
appId, // get appId in path
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
setAppSiderbarExpand: state.setAppSiderbarExpand,
|
||||
})))
|
||||
const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false)
|
||||
const [appDetailRes, setAppDetailRes] = useState<App | null>(null)
|
||||
const [navigation, setNavigation] = useState<Array<{
|
||||
name: string
|
||||
href: string
|
||||
icon: NavIcon
|
||||
selectedIcon: NavIcon
|
||||
}>>([])
|
||||
const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
|
||||
|
||||
const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
|
||||
const navs = [
|
||||
...(isCurrentWorkspaceEditor
|
||||
? [{
|
||||
name: t('common.appMenus.promptEng'),
|
||||
href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`,
|
||||
icon: RiTerminalWindowLine,
|
||||
selectedIcon: RiTerminalWindowFill,
|
||||
}]
|
||||
: []
|
||||
),
|
||||
{
|
||||
name: t('common.appMenus.apiAccess'),
|
||||
href: `/app/${appId}/develop`,
|
||||
icon: RiTerminalBoxLine,
|
||||
selectedIcon: RiTerminalBoxFill,
|
||||
},
|
||||
...(isCurrentWorkspaceEditor
|
||||
? [{
|
||||
name: mode !== 'workflow'
|
||||
? t('common.appMenus.logAndAnn')
|
||||
: t('common.appMenus.logs'),
|
||||
href: `/app/${appId}/logs`,
|
||||
icon: RiFileList3Line,
|
||||
selectedIcon: RiFileList3Fill,
|
||||
}]
|
||||
: []
|
||||
),
|
||||
{
|
||||
name: t('common.appMenus.overview'),
|
||||
href: `/app/${appId}/overview`,
|
||||
icon: RiDashboard2Line,
|
||||
selectedIcon: RiDashboard2Fill,
|
||||
},
|
||||
]
|
||||
return navs
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (appDetail) {
|
||||
document.title = `${(appDetail.name || 'App')} - Dify`
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
// TODO: consider screen size and mode
|
||||
// if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
||||
// setAppSiderbarExpand('collapse')
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appDetail, isMobile])
|
||||
|
||||
useEffect(() => {
|
||||
setAppDetail()
|
||||
setIsLoadingAppDetail(true)
|
||||
fetchAppDetail({ url: '/apps', id: appId }).then((res) => {
|
||||
setAppDetailRes(res)
|
||||
}).catch((e: any) => {
|
||||
if (e.status === 404)
|
||||
router.replace('/apps')
|
||||
}).finally(() => {
|
||||
setIsLoadingAppDetail(false)
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appId, pathname])
|
||||
|
||||
useEffect(() => {
|
||||
if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail)
|
||||
return
|
||||
const res = appDetailRes
|
||||
// redirection
|
||||
const canIEditApp = isCurrentWorkspaceEditor
|
||||
if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) {
|
||||
router.replace(`/app/${appId}/overview`)
|
||||
return
|
||||
}
|
||||
if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) {
|
||||
router.replace(`/app/${appId}/workflow`)
|
||||
}
|
||||
else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) {
|
||||
router.replace(`/app/${appId}/configuration`)
|
||||
}
|
||||
else {
|
||||
setAppDetail({ ...res, enable_sso: false })
|
||||
setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode))
|
||||
if (systemFeatures.enable_web_sso_switch_component && canIEditApp) {
|
||||
fetchAppSSO({ appId }).then((ssoRes) => {
|
||||
setAppDetail({ ...res, enable_sso: ssoRes.enabled })
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appDetailRes, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, systemFeatures.enable_web_sso_switch_component])
|
||||
|
||||
useUnmount(() => {
|
||||
setAppDetail()
|
||||
})
|
||||
|
||||
if (!appDetail) {
|
||||
return (
|
||||
<div className='flex h-full items-center justify-center bg-background-body'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(s.app, 'relative flex', 'overflow-hidden')}>
|
||||
{appDetail && (
|
||||
<AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background as string} desc={appDetail.mode} navigation={navigation} />
|
||||
)}
|
||||
<div className="grow overflow-hidden bg-components-panel-bg">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(AppDetailLayout)
|
||||
@ -1,14 +1,177 @@
|
||||
import Main from './layout-main'
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useUnmount } from 'ahooks'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
RiDashboard2Fill,
|
||||
RiDashboard2Line,
|
||||
RiFileList3Fill,
|
||||
RiFileList3Line,
|
||||
RiTerminalBoxFill,
|
||||
RiTerminalBoxLine,
|
||||
RiTerminalWindowFill,
|
||||
RiTerminalWindowLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import type { NavIcon } from '@/app/components/app-sidebar/navLink'
|
||||
import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
|
||||
import AppContext, { useAppContext } from '@/context/app-context'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import type { App } from '@/types/app'
|
||||
|
||||
const AppDetailLayout = async (props: {
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ appId: string }>
|
||||
}) => {
|
||||
params: { appId: string }
|
||||
}
|
||||
|
||||
const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
params,
|
||||
params: { appId }, // get appId in path
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
setAppSiderbarExpand: state.setAppSiderbarExpand,
|
||||
})))
|
||||
const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false)
|
||||
const [appDetailRes, setAppDetailRes] = useState<App | null>(null)
|
||||
const [navigation, setNavigation] = useState<Array<{
|
||||
name: string
|
||||
href: string
|
||||
icon: NavIcon
|
||||
selectedIcon: NavIcon
|
||||
}>>([])
|
||||
const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
|
||||
|
||||
return <Main appId={(await params).appId}>{children}</Main>
|
||||
const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
|
||||
const navs = [
|
||||
...(isCurrentWorkspaceEditor
|
||||
? [{
|
||||
name: t('common.appMenus.promptEng'),
|
||||
href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`,
|
||||
icon: RiTerminalWindowLine,
|
||||
selectedIcon: RiTerminalWindowFill,
|
||||
}]
|
||||
: []
|
||||
),
|
||||
{
|
||||
name: t('common.appMenus.apiAccess'),
|
||||
href: `/app/${appId}/develop`,
|
||||
icon: RiTerminalBoxLine,
|
||||
selectedIcon: RiTerminalBoxFill,
|
||||
},
|
||||
...(isCurrentWorkspaceEditor
|
||||
? [{
|
||||
name: mode !== 'workflow'
|
||||
? t('common.appMenus.logAndAnn')
|
||||
: t('common.appMenus.logs'),
|
||||
href: `/app/${appId}/logs`,
|
||||
icon: RiFileList3Line,
|
||||
selectedIcon: RiFileList3Fill,
|
||||
}]
|
||||
: []
|
||||
),
|
||||
{
|
||||
name: t('common.appMenus.overview'),
|
||||
href: `/app/${appId}/overview`,
|
||||
icon: RiDashboard2Line,
|
||||
selectedIcon: RiDashboard2Fill,
|
||||
},
|
||||
]
|
||||
return navs
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (appDetail) {
|
||||
document.title = `${(appDetail.name || 'App')} - Dify`
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
// TODO: consider screen size and mode
|
||||
// if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow'))
|
||||
// setAppSiderbarExpand('collapse')
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appDetail, isMobile])
|
||||
|
||||
useEffect(() => {
|
||||
setAppDetail()
|
||||
setIsLoadingAppDetail(true)
|
||||
fetchAppDetail({ url: '/apps', id: appId }).then((res) => {
|
||||
setAppDetailRes(res)
|
||||
}).catch((e: any) => {
|
||||
if (e.status === 404)
|
||||
router.replace('/apps')
|
||||
}).finally(() => {
|
||||
setIsLoadingAppDetail(false)
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appId, pathname])
|
||||
|
||||
useEffect(() => {
|
||||
if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail)
|
||||
return
|
||||
const res = appDetailRes
|
||||
// redirection
|
||||
const canIEditApp = isCurrentWorkspaceEditor
|
||||
if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) {
|
||||
router.replace(`/app/${appId}/overview`)
|
||||
return
|
||||
}
|
||||
if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) {
|
||||
router.replace(`/app/${appId}/workflow`)
|
||||
}
|
||||
else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) {
|
||||
router.replace(`/app/${appId}/configuration`)
|
||||
}
|
||||
else {
|
||||
setAppDetail({ ...res, enable_sso: false })
|
||||
setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode))
|
||||
if (systemFeatures.enable_web_sso_switch_component && canIEditApp) {
|
||||
fetchAppSSO({ appId }).then((ssoRes) => {
|
||||
setAppDetail({ ...res, enable_sso: ssoRes.enabled })
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appDetailRes, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, systemFeatures.enable_web_sso_switch_component])
|
||||
|
||||
useUnmount(() => {
|
||||
setAppDetail()
|
||||
})
|
||||
|
||||
if (!appDetail) {
|
||||
return (
|
||||
<div className='flex h-full items-center justify-center bg-background-body'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(s.app, 'flex relative', 'overflow-hidden')}>
|
||||
{appDetail && (
|
||||
<AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background as string} desc={appDetail.mode} navigation={navigation} />
|
||||
)}
|
||||
<div className="bg-components-panel-bg grow overflow-hidden">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default AppDetailLayout
|
||||
export default React.memo(AppDetailLayout)
|
||||
|
||||
@ -122,7 +122,7 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
||||
return <Loading />
|
||||
|
||||
return (
|
||||
<div className={className || 'mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'}>
|
||||
<div className={className || 'grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'}>
|
||||
<AppCard
|
||||
appInfo={appDetail}
|
||||
cardType="webapp"
|
||||
|
||||
@ -46,7 +46,7 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='system-xl-semibold mb-4 mt-8 flex flex-row items-center text-text-primary'>
|
||||
<div className='flex flex-row items-center mt-8 mb-4 system-xl-semibold text-text-primary'>
|
||||
<span className='mr-3'>{t('appOverview.analysis.title')}</span>
|
||||
<SimpleSelect
|
||||
items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))}
|
||||
@ -61,13 +61,13 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
/>
|
||||
</div>
|
||||
{!isWorkflow && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<ConversationsChart period={period} id={appId} />
|
||||
<EndUsersChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{!isWorkflow && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
{isChatApp
|
||||
? (
|
||||
<AvgSessionInteractions period={period} id={appId} />
|
||||
@ -79,24 +79,24 @@ export default function ChartView({ appId }: IChartViewProps) {
|
||||
</div>
|
||||
)}
|
||||
{!isWorkflow && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<UserSatisfactionRate period={period} id={appId} />
|
||||
<CostChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{!isWorkflow && isChatApp && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<MessagesChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{isWorkflow && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<WorkflowMessagesChart period={period} id={appId} />
|
||||
<WorkflowDailyTerminalsChart period={period} id={appId} />
|
||||
</div>
|
||||
)}
|
||||
{isWorkflow && (
|
||||
<div className='mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'>
|
||||
<div className='grid gap-6 grid-cols-1 xl:grid-cols-2 w-full mb-6'>
|
||||
<WorkflowCostChart period={period} id={appId} />
|
||||
<AvgUserInteractions period={period} id={appId} />
|
||||
</div>
|
||||
|
||||
@ -5,18 +5,14 @@ import TracingPanel from './tracing/panel'
|
||||
import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
|
||||
|
||||
export type IDevelopProps = {
|
||||
params: Promise<{ appId: string }>
|
||||
params: { appId: string }
|
||||
}
|
||||
|
||||
const Overview = async (props: IDevelopProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
appId,
|
||||
} = params
|
||||
|
||||
const Overview = async ({
|
||||
params: { appId },
|
||||
}: IDevelopProps) => {
|
||||
return (
|
||||
<div className="h-full overflow-scroll bg-chatbot-bg px-4 py-6 sm:px-12">
|
||||
<div className="h-full px-4 sm:px-12 py-6 overflow-scroll bg-chatbot-bg">
|
||||
<ApikeyInfoPanel />
|
||||
<TracingPanel />
|
||||
<CardView appId={appId} />
|
||||
|
||||
@ -58,8 +58,8 @@ const ConfigBtn: FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<div className={cn(className, 'rounded-md p-1')}>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<div className={cn(className, 'p-1 rounded-md')}>
|
||||
<RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
|
||||
@ -162,15 +162,15 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-[420px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-4 shadow-xl'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='w-[420px] p-4 rounded-2xl bg-components-panel-bg border-[0.5px] border-components-panel-border shadow-xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center'>
|
||||
<TracingIcon size='md' className='mr-2' />
|
||||
<div className='title-2xl-semi-bold text-text-primary'>{t(`${I18N_PREFIX}.tracing`)}</div>
|
||||
<div className='text-text-primary title-2xl-semi-bold'>{t(`${I18N_PREFIX}.tracing`)}</div>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||
<div className={cn('system-xs-semibold-uppercase ml-1 text-text-tertiary', enabled && 'text-util-colors-green-green-600')}>
|
||||
<div className={cn('ml-1 system-xs-semibold-uppercase text-text-tertiary', enabled && 'text-util-colors-green-green-600')}>
|
||||
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
|
||||
</div>
|
||||
{!readOnly && (
|
||||
@ -189,7 +189,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='system-xs-regular mt-2 text-text-tertiary'>
|
||||
<div className='mt-2 system-xs-regular text-text-tertiary'>
|
||||
{t(`${I18N_PREFIX}.tracingDescription`)}
|
||||
</div>
|
||||
<Divider className='my-3' />
|
||||
@ -211,7 +211,7 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
<div className='mt-2 space-y-2'>
|
||||
{configuredProviderPanel()}
|
||||
</div>
|
||||
<div className='system-xs-medium-uppercase mt-3 text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||
<div className='mt-3 system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||
<div className='mt-2 space-y-2'>
|
||||
{moreProviderPanel()}
|
||||
</div>
|
||||
|
||||
@ -26,7 +26,7 @@ const Field: FC<Props> = ({
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
<div className='flex py-[7px]'>
|
||||
<div className={cn(labelClassName, 'flex h-[18px] items-center text-[13px] font-medium text-text-primary')}>{label} </div>
|
||||
<div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-text-primary')}>{label} </div>
|
||||
{isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
|
||||
</div>
|
||||
<Input
|
||||
|
||||
@ -31,7 +31,7 @@ const Title = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn('system-xl-semibold flex items-center text-text-primary', className)}>
|
||||
<div className={cn('flex items-center system-xl-semibold text-text-primary', className)}>
|
||||
{t('common.appMenus.overview')}
|
||||
</div>
|
||||
)
|
||||
@ -143,7 +143,7 @@ const Panel: FC = () => {
|
||||
}, [setControlShowPopup])
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className='mb-3 flex items-center justify-between'>
|
||||
<div className='flex items-center justify-between mb-3'>
|
||||
<Title className='h-[41px]' />
|
||||
<div className='w-[200px]'>
|
||||
<Loading />
|
||||
@ -153,19 +153,19 @@ const Panel: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('mb-3 flex items-center justify-between')}>
|
||||
<div className={cn('mb-3 flex justify-between items-center')}>
|
||||
<Title className='h-[41px]' />
|
||||
<div
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter',
|
||||
controlShowPopup && 'border-effects-highlight-lightmode-off bg-background-default-lighter',
|
||||
'flex items-center p-2 rounded-xl bg-background-default-dodge border-t border-l-[0.5px] border-effects-highlight shadow-xs cursor-pointer hover:bg-background-default-lighter hover:border-effects-highlight-lightmode-off',
|
||||
controlShowPopup && 'bg-background-default-lighter border-effects-highlight-lightmode-off',
|
||||
)}
|
||||
onClick={showPopup}
|
||||
>
|
||||
{!inUseTracingProvider && (
|
||||
<>
|
||||
<TracingIcon size='md' />
|
||||
<div className='system-sm-semibold mx-2 text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
|
||||
<div className='mx-2 system-sm-semibold text-text-secondary'>{t(`${I18N_PREFIX}.title`)}</div>
|
||||
<div className='flex items-center' onClick={e => e.stopPropagation()}>
|
||||
<ConfigButton
|
||||
appId={appId}
|
||||
@ -184,8 +184,8 @@ const Panel: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<Divider type='vertical' className='h-3.5' />
|
||||
<div className='rounded-md p-1'>
|
||||
<RiArrowDownDoubleLine className='h-4 w-4 text-text-tertiary' />
|
||||
<div className='p-1 rounded-md'>
|
||||
<RiArrowDownDoubleLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -193,7 +193,7 @@ const Panel: FC = () => {
|
||||
<>
|
||||
<div className='ml-4 mr-1 flex items-center'>
|
||||
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||
<div className='system-xs-semibold-uppercase ml-1.5 text-text-tertiary'>
|
||||
<div className='ml-1.5 system-xs-semibold-uppercase text-text-tertiary'>
|
||||
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -167,11 +167,11 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
{!isShowRemoveConfirm
|
||||
? (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='z-[60] h-full w-full'>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-background-overlay'>
|
||||
<div className='mx-2 max-h-[calc(100vh-120px)] w-[640px] overflow-y-auto rounded-2xl bg-components-panel-bg shadow-xl'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='px-8 pt-8'>
|
||||
<div className='mb-4 flex items-center justify-between'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<div className='title-2xl-semi-bold text-text-primary'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div>
|
||||
</div>
|
||||
|
||||
@ -265,14 +265,14 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className='my-8 flex h-8 items-center justify-between'>
|
||||
<div className='my-8 flex justify-between items-center h-8'>
|
||||
<a
|
||||
className='flex items-center space-x-1 text-xs font-normal leading-[18px] text-[#155EEF]'
|
||||
className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]'
|
||||
target='_blank'
|
||||
href={docURL[type]}
|
||||
>
|
||||
<span>{t(`${I18N_PREFIX}.viewDocsLink`, { key: t(`app.tracing.${type}.title`) })}</span>
|
||||
<LinkExternal02 className='h-3 w-3' />
|
||||
<LinkExternal02 className='w-3 h-3' />
|
||||
</a>
|
||||
<div className='flex items-center'>
|
||||
{isEdit && (
|
||||
@ -305,11 +305,11 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-divider-regular'>
|
||||
<div className='flex items-center justify-center bg-background-section-burn py-3 text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 h-3 w-3 text-text-tertiary' />
|
||||
<div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='mx-1 text-primary-600'
|
||||
className='text-primary-600 mx-1'
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||
>
|
||||
|
||||
@ -65,36 +65,36 @@ const ProviderPanel: FC<Props> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl border-[1.5px] bg-background-section-burn px-4 py-3',
|
||||
isChosen ? 'border-components-option-card-option-selected-border bg-background-section' : 'border-transparent',
|
||||
'px-4 py-3 rounded-xl border-[1.5px] bg-background-section-burn',
|
||||
isChosen ? 'bg-background-section border-components-option-card-option-selected-border' : 'border-transparent',
|
||||
!isChosen && hasConfigured && !readOnly && 'cursor-pointer',
|
||||
)}
|
||||
onClick={handleChosen}
|
||||
>
|
||||
<div className={'flex items-center justify-between space-x-1'}>
|
||||
<div className={'flex justify-between items-center space-x-1'}>
|
||||
<div className='flex items-center'>
|
||||
<Icon className='h-6' />
|
||||
{isChosen && <div className='system-2xs-medium-uppercase ml-1 flex h-4 items-center rounded-[4px] border border-text-accent-secondary px-1 text-text-accent-secondary'>{t(`${I18N_PREFIX}.inUse`)}</div>}
|
||||
{isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-text-accent-secondary system-2xs-medium-uppercase text-text-accent-secondary'>{t(`${I18N_PREFIX}.inUse`)}</div>}
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className={'flex items-center justify-between space-x-1'}>
|
||||
<div className={'flex justify-between items-center space-x-1'}>
|
||||
{hasConfigured && (
|
||||
<div className='flex h-6 cursor-pointer items-center space-x-1 rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-text-secondary shadow-xs' onClick={viewBtnClick} >
|
||||
<View className='h-3 w-3' />
|
||||
<div className='flex px-2 items-center h-6 bg-components-button-secondary-bg rounded-md border-[0.5px] border-components-button-secondary-border shadow-xs cursor-pointer text-text-secondary space-x-1' onClick={viewBtnClick} >
|
||||
<View className='w-3 h-3' />
|
||||
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.view`)}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className='flex h-6 cursor-pointer items-center space-x-1 rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-text-secondary shadow-xs'
|
||||
className='flex px-2 items-center h-6 bg-components-button-secondary-bg rounded-md border-[0.5px] border-components-button-secondary-border shadow-xs cursor-pointer text-text-secondary space-x-1'
|
||||
onClick={handleConfigBtnClick}
|
||||
>
|
||||
<RiEqualizer2Line className='h-3 w-3' />
|
||||
<RiEqualizer2Line className='w-3 h-3' />
|
||||
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='system-xs-regular mt-2 text-text-tertiary'>
|
||||
<div className='mt-2 system-xs-regular text-text-tertiary'>
|
||||
{t(`${I18N_PREFIX}.${type}.description`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@ const TracingIcon: FC<Props> = ({
|
||||
const sizeClass = sizeClassMap[size]
|
||||
return (
|
||||
<div className={cn(className, sizeClass, 'bg-primary-500 shadow-md')}>
|
||||
<Icon className='h-full w-full' />
|
||||
<Icon className='w-full h-full' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import Workflow from '@/app/components/workflow'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div className='h-full w-full overflow-x-auto'>
|
||||
<div className='w-full h-full overflow-x-auto'>
|
||||
<Workflow />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -226,37 +226,37 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
}
|
||||
return (
|
||||
<div className="relative w-full py-1" onMouseLeave={onMouseLeave}>
|
||||
<button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickSettings}>
|
||||
<span className='system-sm-regular text-text-secondary'>{t('app.editApp')}</span>
|
||||
<button className='h-8 w-[calc(100%_-_8px)] py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickSettings}>
|
||||
<span className='text-text-secondary system-sm-regular'>{t('app.editApp')}</span>
|
||||
</button>
|
||||
<Divider className="!my-1" />
|
||||
<button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickDuplicate}>
|
||||
<span className='system-sm-regular text-text-secondary'>{t('app.duplicate')}</span>
|
||||
<button className='h-8 w-[calc(100%_-_8px)] py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickDuplicate}>
|
||||
<span className='text-text-secondary system-sm-regular'>{t('app.duplicate')}</span>
|
||||
</button>
|
||||
<button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickExport}>
|
||||
<span className='system-sm-regular text-text-secondary'>{t('app.export')}</span>
|
||||
<button className='h-8 w-[calc(100%_-_8px)] py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickExport}>
|
||||
<span className='text-text-secondary system-sm-regular'>{t('app.export')}</span>
|
||||
</button>
|
||||
{(app.mode === 'completion' || app.mode === 'chat') && (
|
||||
<>
|
||||
<Divider className="!my-1" />
|
||||
<div
|
||||
className='mx-1 flex h-9 cursor-pointer items-center rounded-lg px-3 py-2 hover:bg-state-base-hover'
|
||||
className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-state-base-hover rounded-lg cursor-pointer'
|
||||
onClick={onClickSwitch}
|
||||
>
|
||||
<span className='text-sm leading-5 text-text-secondary'>{t('app.switch')}</span>
|
||||
<span className='text-text-secondary text-sm leading-5'>{t('app.switch')}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Divider className="!my-1" />
|
||||
<button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickInstalledApp}>
|
||||
<span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span>
|
||||
<button className='h-8 w-[calc(100%_-_8px)] py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickInstalledApp}>
|
||||
<span className='text-text-secondary system-sm-regular'>{t('app.openInExplore')}</span>
|
||||
</button>
|
||||
<Divider className="!my-1" />
|
||||
<div
|
||||
className='group mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover'
|
||||
className='group h-8 w-[calc(100%_-_8px)] py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-destructive-hover rounded-lg cursor-pointer'
|
||||
onClick={onClickDelete}
|
||||
>
|
||||
<span className='system-sm-regular text-text-secondary group-hover:text-text-destructive'>
|
||||
<span className='text-text-secondary system-sm-regular group-hover:text-text-destructive'>
|
||||
{t('common.operation.delete')}
|
||||
</span>
|
||||
</div>
|
||||
@ -276,9 +276,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
e.preventDefault()
|
||||
getRedirection(isCurrentWorkspaceEditor, app, push)
|
||||
}}
|
||||
className='group relative col-span-1 inline-flex h-[160px] cursor-pointer flex-col rounded-xl border-[1px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg'
|
||||
className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
>
|
||||
<div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size="large"
|
||||
@ -287,13 +287,13 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
background={app.icon_background}
|
||||
imageUrl={app.icon_url}
|
||||
/>
|
||||
<AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='h-3 w-3' />
|
||||
<AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='w-3 h-3' />
|
||||
</div>
|
||||
<div className='w-0 grow py-[1px]'>
|
||||
<div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'>
|
||||
<div className='grow w-0 py-[1px]'>
|
||||
<div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'>
|
||||
<div className='truncate' title={app.name}>{app.name}</div>
|
||||
</div>
|
||||
<div className='flex items-center text-[10px] font-medium leading-[18px] text-text-tertiary'>
|
||||
<div className='flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium'>
|
||||
{app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>}
|
||||
{app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>}
|
||||
{app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>}
|
||||
@ -311,17 +311,17 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(
|
||||
'absolute bottom-1 left-0 right-0 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1',
|
||||
'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
|
||||
tags.length ? 'flex' : '!hidden group-hover:!flex',
|
||||
)}>
|
||||
{isCurrentWorkspaceEditor && (
|
||||
<>
|
||||
<div className={cn('flex w-0 grow items-center gap-1')} onClick={(e) => {
|
||||
<div className={cn('grow flex items-center gap-1 w-0')} onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}>
|
||||
<div className={cn(
|
||||
'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block',
|
||||
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
|
||||
tags.length ? '!block' : '!hidden',
|
||||
)}>
|
||||
<TagSelector
|
||||
@ -335,23 +335,23 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 group-hover:!flex' />
|
||||
<div className='!hidden shrink-0 group-hover:!flex'>
|
||||
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]' />
|
||||
<div className='!hidden group-hover:!flex shrink-0'>
|
||||
<CustomPopover
|
||||
htmlContent={<Operations />}
|
||||
position="br"
|
||||
trigger="click"
|
||||
btnElement={
|
||||
<div
|
||||
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md'
|
||||
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
|
||||
>
|
||||
<RiMoreFill className='h-4 w-4 text-text-tertiary' />
|
||||
<RiMoreFill className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
}
|
||||
btnClassName={open =>
|
||||
cn(
|
||||
open ? '!bg-black/5 !shadow-none' : '!bg-transparent',
|
||||
'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5',
|
||||
'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5',
|
||||
)
|
||||
}
|
||||
popupClassName={
|
||||
@ -359,7 +359,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
? '!w-[256px] translate-x-[-224px]'
|
||||
: '!w-[160px] translate-x-[-128px]'
|
||||
}
|
||||
className={'!z-20 h-fit'}
|
||||
className={'h-fit !z-20'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -8,7 +8,6 @@ import { useDebounceFn } from 'ahooks'
|
||||
import {
|
||||
RiApps2Line,
|
||||
RiExchange2Line,
|
||||
RiFile4Line,
|
||||
RiMessage3Line,
|
||||
RiRobot3Line,
|
||||
} from '@remixicon/react'
|
||||
@ -79,12 +78,10 @@ const Apps = () => {
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const options = [
|
||||
{ value: 'all', text: t('app.types.all'), icon: <RiApps2Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'chat', text: t('app.types.chatbot'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'agent-chat', text: t('app.types.agent'), icon: <RiRobot3Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'completion', text: t('app.types.completion'), icon: <RiFile4Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'advanced-chat', text: t('app.types.advanced'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='mr-1 h-[14px] w-[14px]' /> },
|
||||
{ value: 'all', text: t('app.types.all'), icon: <RiApps2Line className='w-[14px] h-[14px] mr-1' /> },
|
||||
{ value: 'chat', text: t('app.types.chatbot'), icon: <RiMessage3Line className='w-[14px] h-[14px] mr-1' /> },
|
||||
{ value: 'agent-chat', text: t('app.types.agent'), icon: <RiRobot3Line className='w-[14px] h-[14px] mr-1' /> },
|
||||
{ value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='w-[14px] h-[14px] mr-1' /> },
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
@ -137,7 +134,7 @@ const Apps = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='sticky top-0 z-10 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'>
|
||||
<div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
||||
<TabSliderNew
|
||||
value={activeTab}
|
||||
onChange={setActiveTab}
|
||||
@ -162,14 +159,14 @@ const Apps = () => {
|
||||
</div>
|
||||
</div>
|
||||
{(data && data[0].total > 0)
|
||||
? <div className='relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||
? <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard onSuccess={mutate} />}
|
||||
{data.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
)))}
|
||||
</div>
|
||||
: <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||
: <div className='grid content-start grid-cols-1 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6 gap-4 px-12 pt-2 grow relative overflow-hidden'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard className='z-10' onSuccess={mutate} />}
|
||||
<NoAppsFound />
|
||||
@ -189,14 +186,14 @@ function NoAppsFound() {
|
||||
const { t } = useTranslation()
|
||||
function renderDefaultCard() {
|
||||
const defaultCards = Array.from({ length: 36 }, (_, index) => (
|
||||
<div key={index} className='inline-flex h-[160px] rounded-xl bg-background-default-lighter'></div>
|
||||
<div key={index} className='h-[160px] inline-flex rounded-xl bg-background-default-lighter'></div>
|
||||
))
|
||||
return defaultCards
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{renderDefaultCard()}
|
||||
<div className='absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'>
|
||||
<div className='absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'>
|
||||
<span className='system-md-medium text-text-tertiary'>{t('app.newApp.noAppsFound')}</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { forwardRef, useMemo, useState } from 'react'
|
||||
import {
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
@ -18,15 +18,7 @@ export type CreateAppCardProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
const CreateAppCard = (
|
||||
{
|
||||
ref,
|
||||
className,
|
||||
onSuccess,
|
||||
}: CreateAppCardProps & {
|
||||
ref: React.RefObject<HTMLDivElement>;
|
||||
},
|
||||
) => {
|
||||
const CreateAppCard = forwardRef<HTMLDivElement, CreateAppCardProps>(({ className, onSuccess }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { onPlanInfoChanged } = useProviderContext()
|
||||
const searchParams = useSearchParams()
|
||||
@ -47,22 +39,22 @@ const CreateAppCard = (
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('relative col-span-1 inline-flex h-[160px] flex-col justify-between rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg', className)}
|
||||
className={cn('relative col-span-1 inline-flex flex-col justify-between h-[160px] bg-components-card-bg rounded-xl border-[0.5px] border-components-card-border', className)}
|
||||
>
|
||||
<div className='grow rounded-t-xl p-2'>
|
||||
<div className='px-6 pb-1 pt-2 text-xs font-medium leading-[18px] text-text-tertiary'>{t('app.createApp')}</div>
|
||||
<button className='mb-1 flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' onClick={() => setShowNewAppModal(true)}>
|
||||
<FilePlus01 className='mr-2 h-4 w-4 shrink-0' />
|
||||
<div className='grow p-2 rounded-t-xl'>
|
||||
<div className='px-6 pt-2 pb-1 text-xs font-medium leading-[18px] text-text-tertiary'>{t('app.createApp')}</div>
|
||||
<button className='w-full flex items-center mb-1 px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppModal(true)}>
|
||||
<FilePlus01 className='shrink-0 mr-2 w-4 h-4' />
|
||||
{t('app.newApp.startFromBlank')}
|
||||
</button>
|
||||
<button className='flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' onClick={() => setShowNewAppTemplateDialog(true)}>
|
||||
<FilePlus02 className='mr-2 h-4 w-4 shrink-0' />
|
||||
<button className='w-full flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover' onClick={() => setShowNewAppTemplateDialog(true)}>
|
||||
<FilePlus02 className='shrink-0 mr-2 w-4 h-4' />
|
||||
{t('app.newApp.startFromTemplate')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreateFromDSLModal(true)}
|
||||
className='flex w-full cursor-pointer items-center rounded-lg px-6 py-[7px] text-[13px] font-medium leading-[18px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<FileArrow01 className='mr-2 h-4 w-4 shrink-0' />
|
||||
className='w-full flex items-center px-6 py-[7px] rounded-lg text-[13px] font-medium leading-[18px] text-text-tertiary cursor-pointer hover:text-text-secondary hover:bg-state-base-hover'>
|
||||
<FileArrow01 className='shrink-0 mr-2 w-4 h-4' />
|
||||
{t('app.importDSL')}
|
||||
</button>
|
||||
</div>
|
||||
@ -111,7 +103,7 @@ const CreateAppCard = (
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
CreateAppCard.displayName = 'CreateAppCard'
|
||||
export default CreateAppCard
|
||||
|
||||
@ -13,17 +13,17 @@ const AppList = () => {
|
||||
const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures)
|
||||
|
||||
return (
|
||||
<div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'>
|
||||
<div className='relative flex flex-col overflow-y-auto bg-background-body shrink-0 h-0 grow'>
|
||||
<Apps />
|
||||
{systemFeatures.license.status === LicenseStatus.NONE && <footer className='shrink-0 grow-0 px-12 py-6'>
|
||||
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3>
|
||||
<p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p>
|
||||
<div className='mt-3 flex items-center gap-2'>
|
||||
{systemFeatures.license.status === LicenseStatus.NONE && <footer className='px-12 py-6 grow-0 shrink-0'>
|
||||
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('app.join')}</h3>
|
||||
<p className='mt-1 system-sm-regular text-text-tertiary'>{t('app.communityIntro')}</p>
|
||||
<div className='flex items-center gap-2 mt-3'>
|
||||
<Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://github.com/langgenius/dify'>
|
||||
<RiGithubFill className='h-5 w-5 text-text-tertiary' />
|
||||
<RiGithubFill className='w-5 h-5 text-text-tertiary' />
|
||||
</Link>
|
||||
<Link className={style.socialMediaLink} target='_blank' rel='noopener noreferrer' href='https://discord.gg/FngNHpbcY7'>
|
||||
<RiDiscordFill className='h-5 w-5 text-text-tertiary' />
|
||||
<RiDiscordFill className='w-5 h-5 text-text-tertiary' />
|
||||
</Link>
|
||||
</div>
|
||||
</footer>}
|
||||
|
||||
@ -2,17 +2,12 @@ import React from 'react'
|
||||
import MainDetail from '@/app/components/datasets/documents/detail'
|
||||
|
||||
export type IDocumentDetailProps = {
|
||||
params: Promise<{ datasetId: string; documentId: string }>
|
||||
params: { datasetId: string; documentId: string }
|
||||
}
|
||||
|
||||
const DocumentDetail = async (props: IDocumentDetailProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
datasetId,
|
||||
documentId,
|
||||
} = params
|
||||
|
||||
const DocumentDetail = async ({
|
||||
params: { datasetId, documentId },
|
||||
}: IDocumentDetailProps) => {
|
||||
return (
|
||||
<MainDetail datasetId={datasetId} documentId={documentId} />
|
||||
)
|
||||
|
||||
@ -2,17 +2,12 @@ import React from 'react'
|
||||
import Settings from '@/app/components/datasets/documents/detail/settings'
|
||||
|
||||
export type IProps = {
|
||||
params: Promise<{ datasetId: string; documentId: string }>
|
||||
params: { datasetId: string; documentId: string }
|
||||
}
|
||||
|
||||
const DocumentSettings = async (props: IProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
datasetId,
|
||||
documentId,
|
||||
} = params
|
||||
|
||||
const DocumentSettings = async ({
|
||||
params: { datasetId, documentId },
|
||||
}: IProps) => {
|
||||
return (
|
||||
<Settings datasetId={datasetId} documentId={documentId} />
|
||||
)
|
||||
|
||||
@ -2,16 +2,12 @@ import React from 'react'
|
||||
import DatasetUpdateForm from '@/app/components/datasets/create'
|
||||
|
||||
export type IProps = {
|
||||
params: Promise<{ datasetId: string }>
|
||||
params: { datasetId: string }
|
||||
}
|
||||
|
||||
const Create = async (props: IProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
datasetId,
|
||||
} = params
|
||||
|
||||
const Create = async ({
|
||||
params: { datasetId },
|
||||
}: IProps) => {
|
||||
return (
|
||||
<DatasetUpdateForm datasetId={datasetId} />
|
||||
)
|
||||
|
||||
@ -2,16 +2,12 @@ import React from 'react'
|
||||
import Main from '@/app/components/datasets/documents'
|
||||
|
||||
export type IProps = {
|
||||
params: Promise<{ datasetId: string }>
|
||||
params: { datasetId: string }
|
||||
}
|
||||
|
||||
const Documents = async (props: IProps) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
datasetId,
|
||||
} = params
|
||||
|
||||
const Documents = async ({
|
||||
params: { datasetId },
|
||||
}: IProps) => {
|
||||
return (
|
||||
<Main datasetId={datasetId} />
|
||||
)
|
||||
|
||||
@ -2,16 +2,12 @@ import React from 'react'
|
||||
import Main from '@/app/components/datasets/hit-testing'
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ datasetId: string }>
|
||||
params: { datasetId: string }
|
||||
}
|
||||
|
||||
const HitTesting = async (props: Props) => {
|
||||
const params = await props.params
|
||||
|
||||
const {
|
||||
datasetId,
|
||||
} = params
|
||||
|
||||
const HitTesting = ({
|
||||
params: { datasetId },
|
||||
}: Props) => {
|
||||
return (
|
||||
<Main datasetId={datasetId} />
|
||||
)
|
||||
|
||||
@ -1,221 +0,0 @@
|
||||
'use client'
|
||||
import type { FC, SVGProps } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
DocumentTextIcon,
|
||||
PaperClipIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import {
|
||||
Cog8ToothIcon as Cog8ToothSolidIcon,
|
||||
// CommandLineIcon as CommandLineSolidIcon,
|
||||
DocumentTextIcon as DocumentTextSolidIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import { RiApps2AddLine, RiBookOpenLine, RiInformation2Line } from '@remixicon/react'
|
||||
import s from './style.module.css'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
|
||||
import type { RelatedAppResponse } from '@/models/datasets'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
params: { datasetId: string }
|
||||
}
|
||||
|
||||
const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
|
||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
|
||||
<g clipPath="url(#clip0_4610_6951)">
|
||||
<path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4610_6951">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
|
||||
const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
|
||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
|
||||
<path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
|
||||
<path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
|
||||
</svg>
|
||||
}
|
||||
|
||||
type IExtraInfoProps = {
|
||||
isMobile: boolean
|
||||
relatedApps?: RelatedAppResponse
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
|
||||
const locale = getLocaleOnClient()
|
||||
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
|
||||
const relatedAppsTotal = relatedApps?.data?.length || 0
|
||||
|
||||
useEffect(() => {
|
||||
setShowTips(!isMobile)
|
||||
}, [isMobile, setShowTips])
|
||||
|
||||
return <div>
|
||||
{hasRelatedApps && (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<Tooltip
|
||||
position='right'
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
<LinkedAppsPanel
|
||||
relatedApps={relatedApps.data}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className='system-xs-medium-uppercase inline-flex cursor-pointer items-center space-x-1 text-text-secondary'>
|
||||
<span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span>
|
||||
<RiInformation2Line className='h-4 w-4' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
|
||||
{relatedAppsTotal || '--'}
|
||||
<PaperClipIcon className='h-4 w-4 text-gray-700' />
|
||||
</div>}
|
||||
</>
|
||||
)}
|
||||
{!hasRelatedApps && !expand && (
|
||||
<Tooltip
|
||||
position='right'
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
<div className='w-[240px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-4'>
|
||||
<div className='inline-flex rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle p-2'>
|
||||
<RiApps2AddLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div>
|
||||
<a
|
||||
className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent'
|
||||
href={
|
||||
locale === LanguagesSupported[1]
|
||||
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
|
||||
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
|
||||
}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
>
|
||||
<RiBookOpenLine className='mr-1 text-text-accent' />
|
||||
{t('common.datasetMenus.viewDoc')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='system-xs-medium-uppercase inline-flex cursor-pointer items-center space-x-1 text-text-secondary'>
|
||||
<span>{t('common.datasetMenus.noRelatedApp')}</span>
|
||||
<RiInformation2Line className='h-4 w-4' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
params: { datasetId },
|
||||
} = props
|
||||
const pathname = usePathname()
|
||||
const hideSideBar = /documents\/create$/.test(pathname)
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
|
||||
url: 'fetchDatasetDetail',
|
||||
datasetId,
|
||||
}, apiParams => fetchDatasetDetail(apiParams.datasetId))
|
||||
|
||||
const { data: relatedApps } = useSWR({
|
||||
action: 'fetchDatasetRelatedApps',
|
||||
datasetId,
|
||||
}, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
|
||||
|
||||
const navigation = useMemo(() => {
|
||||
const baseNavigation = [
|
||||
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
|
||||
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
|
||||
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
|
||||
]
|
||||
|
||||
if (datasetRes?.provider !== 'external') {
|
||||
baseNavigation.unshift({
|
||||
name: t('common.datasetMenus.documents'),
|
||||
href: `/datasets/${datasetId}/documents`,
|
||||
icon: DocumentTextIcon,
|
||||
selectedIcon: DocumentTextSolidIcon,
|
||||
})
|
||||
}
|
||||
return baseNavigation
|
||||
}, [datasetRes?.provider, datasetId, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetRes)
|
||||
document.title = `${datasetRes.name || 'Dataset'} - Dify`
|
||||
}, [datasetRes])
|
||||
|
||||
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
|
||||
|
||||
useEffect(() => {
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
}, [isMobile, setAppSiderbarExpand])
|
||||
|
||||
if (!datasetRes && !error)
|
||||
return <Loading type='app' />
|
||||
|
||||
return (
|
||||
<div className='flex grow overflow-hidden'>
|
||||
{!hideSideBar && <AppSideBar
|
||||
title={datasetRes?.name || '--'}
|
||||
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
|
||||
icon_background={datasetRes?.icon_background || '#F5F5F5'}
|
||||
desc={datasetRes?.description || '--'}
|
||||
isExternal={datasetRes?.provider === 'external'}
|
||||
navigation={navigation}
|
||||
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined}
|
||||
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
|
||||
/>}
|
||||
<DatasetDetailContext.Provider value={{
|
||||
indexingTechnique: datasetRes?.indexing_technique,
|
||||
dataset: datasetRes,
|
||||
mutateDatasetRes: () => mutateDatasetRes(),
|
||||
}}>
|
||||
<div className="grow overflow-hidden bg-background-default-subtle">{children}</div>
|
||||
</DatasetDetailContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(DatasetDetailLayout)
|
||||
@ -1,17 +1,221 @@
|
||||
import Main from './layout-main'
|
||||
'use client'
|
||||
import type { FC, SVGProps } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
DocumentTextIcon,
|
||||
PaperClipIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import {
|
||||
Cog8ToothIcon as Cog8ToothSolidIcon,
|
||||
// CommandLineIcon as CommandLineSolidIcon,
|
||||
DocumentTextIcon as DocumentTextSolidIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import { RiApps2AddLine, RiBookOpenLine, RiInformation2Line } from '@remixicon/react'
|
||||
import s from './style.module.css'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
|
||||
import type { RelatedAppResponse } from '@/models/datasets'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import LinkedAppsPanel from '@/app/components/base/linked-apps-panel'
|
||||
|
||||
const DatasetDetailLayout = async (
|
||||
props: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ datasetId: string }>
|
||||
},
|
||||
) => {
|
||||
const params = await props.params
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
params: { datasetId: string }
|
||||
}
|
||||
|
||||
const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
|
||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
|
||||
<g clipPath="url(#clip0_4610_6951)">
|
||||
<path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4610_6951">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
|
||||
const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
|
||||
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
|
||||
<path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
|
||||
<path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
|
||||
</svg>
|
||||
}
|
||||
|
||||
type IExtraInfoProps = {
|
||||
isMobile: boolean
|
||||
relatedApps?: RelatedAppResponse
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => {
|
||||
const locale = getLocaleOnClient()
|
||||
const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0
|
||||
const relatedAppsTotal = relatedApps?.data?.length || 0
|
||||
|
||||
useEffect(() => {
|
||||
setShowTips(!isMobile)
|
||||
}, [isMobile, setShowTips])
|
||||
|
||||
return <div>
|
||||
{hasRelatedApps && (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<Tooltip
|
||||
position='right'
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
<LinkedAppsPanel
|
||||
relatedApps={relatedApps.data}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
|
||||
<span>{relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')}</span>
|
||||
<RiInformation2Line className='w-4 h-4' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
|
||||
{relatedAppsTotal || '--'}
|
||||
<PaperClipIcon className='h-4 w-4 text-gray-700' />
|
||||
</div>}
|
||||
</>
|
||||
)}
|
||||
{!hasRelatedApps && !expand && (
|
||||
<Tooltip
|
||||
position='right'
|
||||
noDecoration
|
||||
needsDelay
|
||||
popupContent={
|
||||
<div className='p-4 w-[240px] bg-components-panel-bg-blur border-[0.5px] border-components-panel-border rounded-xl'>
|
||||
<div className='inline-flex p-2 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle'>
|
||||
<RiApps2AddLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='text-xs text-text-tertiary my-2'>{t('common.datasetMenus.emptyTip')}</div>
|
||||
<a
|
||||
className='inline-flex items-center text-xs text-text-accent mt-2 cursor-pointer'
|
||||
href={
|
||||
locale === LanguagesSupported[1]
|
||||
? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application'
|
||||
: 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application'
|
||||
}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
>
|
||||
<RiBookOpenLine className='mr-1 text-text-accent' />
|
||||
{t('common.datasetMenus.viewDoc')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='inline-flex items-center system-xs-medium-uppercase text-text-secondary space-x-1 cursor-pointer'>
|
||||
<span>{t('common.datasetMenus.noRelatedApp')}</span>
|
||||
<RiInformation2Line className='w-4 h-4' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
params: { datasetId },
|
||||
} = props
|
||||
const pathname = usePathname()
|
||||
const hideSideBar = /documents\/create$/.test(pathname)
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
|
||||
return <Main params={(await params)}>{children}</Main>
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
|
||||
url: 'fetchDatasetDetail',
|
||||
datasetId,
|
||||
}, apiParams => fetchDatasetDetail(apiParams.datasetId))
|
||||
|
||||
const { data: relatedApps } = useSWR({
|
||||
action: 'fetchDatasetRelatedApps',
|
||||
datasetId,
|
||||
}, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
|
||||
|
||||
const navigation = useMemo(() => {
|
||||
const baseNavigation = [
|
||||
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
|
||||
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
|
||||
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
|
||||
]
|
||||
|
||||
if (datasetRes?.provider !== 'external') {
|
||||
baseNavigation.unshift({
|
||||
name: t('common.datasetMenus.documents'),
|
||||
href: `/datasets/${datasetId}/documents`,
|
||||
icon: DocumentTextIcon,
|
||||
selectedIcon: DocumentTextSolidIcon,
|
||||
})
|
||||
}
|
||||
return baseNavigation
|
||||
}, [datasetRes?.provider, datasetId, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetRes)
|
||||
document.title = `${datasetRes.name || 'Dataset'} - Dify`
|
||||
}, [datasetRes])
|
||||
|
||||
const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
|
||||
|
||||
useEffect(() => {
|
||||
const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
|
||||
const mode = isMobile ? 'collapse' : 'expand'
|
||||
setAppSiderbarExpand(isMobile ? mode : localeMode)
|
||||
}, [isMobile, setAppSiderbarExpand])
|
||||
|
||||
if (!datasetRes && !error)
|
||||
return <Loading type='app' />
|
||||
|
||||
return (
|
||||
<div className='grow flex overflow-hidden'>
|
||||
{!hideSideBar && <AppSideBar
|
||||
title={datasetRes?.name || '--'}
|
||||
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
|
||||
icon_background={datasetRes?.icon_background || '#F5F5F5'}
|
||||
desc={datasetRes?.description || '--'}
|
||||
isExternal={datasetRes?.provider === 'external'}
|
||||
navigation={navigation}
|
||||
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} expand={mode === 'collapse'} /> : undefined}
|
||||
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
|
||||
/>}
|
||||
<DatasetDetailContext.Provider value={{
|
||||
indexingTechnique: datasetRes?.indexing_technique,
|
||||
dataset: datasetRes,
|
||||
mutateDatasetRes: () => mutateDatasetRes(),
|
||||
}}>
|
||||
<div className="bg-background-default-subtle grow overflow-hidden">{children}</div>
|
||||
</DatasetDetailContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default DatasetDetailLayout
|
||||
export default React.memo(DatasetDetailLayout)
|
||||
|
||||
@ -3,13 +3,13 @@ import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
|
||||
import Form from '@/app/components/datasets/settings/form'
|
||||
|
||||
const Settings = async () => {
|
||||
const locale = await getLocaleOnServer()
|
||||
const locale = getLocaleOnServer()
|
||||
const { t } = await translate(locale, 'dataset-settings')
|
||||
|
||||
return (
|
||||
<div className='h-full overflow-y-auto'>
|
||||
<div className='px-6 py-3'>
|
||||
<div className='system-xl-semibold mb-1 text-text-primary'>{t('title')}</div>
|
||||
<div className='mb-1 system-xl-semibold text-text-primary'>{t('title')}</div>
|
||||
<div className='system-sm-regular text-text-tertiary'>{t('desc')}</div>
|
||||
</div>
|
||||
<Form />
|
||||
|
||||
@ -15,22 +15,22 @@ const ApiServer: FC<ApiServerProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex flex-wrap items-center gap-y-2'>
|
||||
<div className='mr-2 flex h-8 items-center rounded-lg border-[0.5px] border-white bg-white/80 pl-1.5 pr-1 leading-5'>
|
||||
<div className='mr-0.5 h-5 shrink-0 rounded-md border border-gray-200 px-1.5 text-[11px] text-gray-500'>{t('appApi.apiServer')}</div>
|
||||
<div className='w-fit truncate px-1 text-[13px] font-medium text-gray-800 sm:w-[248px]'>{apiBaseUrl}</div>
|
||||
<div className='mx-1 h-[14px] w-[1px] bg-gray-200'></div>
|
||||
<div className='flex items-center flex-wrap gap-y-2'>
|
||||
<div className='flex items-center mr-2 pl-1.5 pr-1 h-8 bg-white/80 border-[0.5px] border-white rounded-lg leading-5'>
|
||||
<div className='mr-0.5 px-1.5 h-5 border border-gray-200 text-[11px] text-gray-500 rounded-md shrink-0'>{t('appApi.apiServer')}</div>
|
||||
<div className='px-1 truncate w-fit sm:w-[248px] text-[13px] font-medium text-gray-800'>{apiBaseUrl}</div>
|
||||
<div className='mx-1 w-[1px] h-[14px] bg-gray-200'></div>
|
||||
<CopyFeedback
|
||||
content={apiBaseUrl}
|
||||
selectorId={randomString(8)}
|
||||
className={'!h-6 !w-6 hover:bg-gray-200'}
|
||||
className={'!w-6 !h-6 hover:bg-gray-200'}
|
||||
/>
|
||||
</div>
|
||||
<div className='mr-2 flex h-8 items-center rounded-lg border-[0.5px] border-[#D1FADF] bg-[#ECFDF3] px-3 text-xs font-semibold text-[#039855]'>
|
||||
<div className='flex items-center mr-2 px-3 h-8 bg-[#ECFDF3] text-xs font-semibold text-[#039855] rounded-lg border-[0.5px] border-[#D1FADF]'>
|
||||
{t('appApi.ok')}
|
||||
</div>
|
||||
<SecretKeyButton
|
||||
className='!h-8 shrink-0 bg-white'
|
||||
className='shrink-0 !h-8 bg-white'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -82,8 +82,8 @@ const Container = () => {
|
||||
}, [currentWorkspace, router])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className='scroll-container relative flex grow flex-col overflow-y-auto bg-background-body'>
|
||||
<div className='sticky top-0 z-10 flex flex-wrap justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'>
|
||||
<div ref={containerRef} className='grow relative flex flex-col bg-background-body overflow-y-auto scroll-container'>
|
||||
<div className='sticky top-0 flex justify-between pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
|
||||
<TabSliderNew
|
||||
value={activeTab}
|
||||
onChange={newActiveTab => setActiveTab(newActiveTab)}
|
||||
@ -108,13 +108,13 @@ const Container = () => {
|
||||
onChange={e => handleKeywordsChange(e.target.value)}
|
||||
onClear={() => handleKeywordsChange('')}
|
||||
/>
|
||||
<div className="h-4 w-[1px] bg-divider-regular" />
|
||||
<div className="w-[1px] h-4 bg-divider-regular" />
|
||||
<Button
|
||||
className='shadows-shadow-xs gap-0.5'
|
||||
className='gap-0.5 shadows-shadow-xs'
|
||||
onClick={() => setShowExternalApiPanel(true)}
|
||||
>
|
||||
<ApiConnectionMod className='h-4 w-4 text-components-button-secondary-text' />
|
||||
<div className='system-sm-medium flex items-center justify-center gap-1 px-0.5 text-components-button-secondary-text'>{t('dataset.externalAPIPanelTitle')}</div>
|
||||
<ApiConnectionMod className='w-4 h-4 text-components-button-secondary-text' />
|
||||
<div className='flex px-0.5 justify-center items-center gap-1 text-components-button-secondary-text system-sm-medium'>{t('dataset.externalAPIPanelTitle')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -84,17 +84,17 @@ const DatasetCard = ({
|
||||
}
|
||||
return (
|
||||
<div className="relative w-full py-1" onMouseLeave={onMouseLeave}>
|
||||
<div className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickRename}>
|
||||
<span className='text-sm text-text-secondary'>{t('common.operation.settings')}</span>
|
||||
<div className='h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={onClickRename}>
|
||||
<span className='text-text-secondary text-sm'>{t('common.operation.settings')}</span>
|
||||
</div>
|
||||
{props.showDelete && (
|
||||
<>
|
||||
<Divider className="!my-1" />
|
||||
<div
|
||||
className='group mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover'
|
||||
className='group h-8 py-[6px] px-3 mx-1 flex items-center gap-2 hover:bg-state-destructive-hover rounded-lg cursor-pointer'
|
||||
onClick={onClickDelete}
|
||||
>
|
||||
<span className={cn('text-sm text-text-secondary', 'group-hover:text-text-destructive')}>
|
||||
<span className={cn('text-text-secondary text-sm', 'group-hover:text-text-destructive')}>
|
||||
{t('common.operation.delete')}
|
||||
</span>
|
||||
</div>
|
||||
@ -111,7 +111,7 @@ const DatasetCard = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='group relative col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg'
|
||||
className='group relative col-span-1 bg-components-card-bg border-[0.5px] border-solid border-components-card-border rounded-xl shadow-sm min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
|
||||
data-disable-nprogress={true}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@ -121,25 +121,25 @@ const DatasetCard = ({
|
||||
}}
|
||||
>
|
||||
{isExternalProvider(dataset.provider) && <CornerLabel label='External' className='absolute right-0' labelClassName='rounded-tr-xl' />}
|
||||
<div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'>
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className={cn(
|
||||
'flex shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF] p-2.5',
|
||||
'shrink-0 flex items-center justify-center p-2.5 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]',
|
||||
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
|
||||
)}>
|
||||
<Folder className='h-5 w-5 text-[#444CE7]' />
|
||||
<Folder className='w-5 h-5 text-[#444CE7]' />
|
||||
</div>
|
||||
<div className='w-0 grow py-[1px]'>
|
||||
<div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'>
|
||||
<div className={cn('truncate', !dataset.embedding_available && 'text-text-tertiary opacity-50 hover:opacity-100')} title={dataset.name}>{dataset.name}</div>
|
||||
<div className='grow w-0 py-[1px]'>
|
||||
<div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'>
|
||||
<div className={cn('truncate', !dataset.embedding_available && 'opacity-50 hover:opacity-100 text-text-tertiary')} title={dataset.name}>{dataset.name}</div>
|
||||
{!dataset.embedding_available && (
|
||||
<Tooltip
|
||||
popupContent={t('dataset.unavailableTip')}
|
||||
>
|
||||
<span className='ml-1 inline-flex w-max shrink-0 rounded-md border border-divider-regular px-1 text-xs font-normal leading-[18px] text-text-tertiary'>{t('dataset.unavailable')}</span>
|
||||
<span className='shrink-0 inline-flex w-max ml-1 px-1 border border-divider-regular rounded-md text-text-tertiary text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className='mt-[1px] flex items-center text-xs leading-[18px] text-text-tertiary'>
|
||||
<div className='flex items-center mt-[1px] text-xs leading-[18px] text-text-tertiary'>
|
||||
<div
|
||||
className={cn('truncate', (!dataset.embedding_available || !dataset.document_count) && 'opacity-50')}
|
||||
title={dataset.provider === 'external' ? `${dataset.app_count}${t('dataset.appCount')}` : `${dataset.document_count}${t('dataset.documentCount')} · ${Math.round(dataset.word_count / 1000)}${t('dataset.wordCount')} · ${dataset.app_count}${t('dataset.appCount')}`}
|
||||
@ -150,9 +150,9 @@ const DatasetCard = ({
|
||||
</>
|
||||
: <>
|
||||
<span>{dataset.document_count}{t('dataset.documentCount')}</span>
|
||||
<span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span>
|
||||
<span className='shrink-0 mx-0.5 w-1 text-text-tertiary'>·</span>
|
||||
<span>{Math.round(dataset.word_count / 1000)}{t('dataset.wordCount')}</span>
|
||||
<span className='mx-0.5 w-1 shrink-0 text-text-tertiary'>·</span>
|
||||
<span className='shrink-0 mx-0.5 w-1 text-text-tertiary'>·</span>
|
||||
<span>{dataset.app_count}{t('dataset.appCount')}</span>
|
||||
</>
|
||||
}
|
||||
@ -162,7 +162,7 @@ const DatasetCard = ({
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'mb-2 max-h-[72px] grow px-[14px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]',
|
||||
'grow mb-2 px-[14px] max-h-[72px] text-xs leading-normal text-text-tertiary group-hover:line-clamp-2 group-hover:max-h-[36px]',
|
||||
tags.length ? 'line-clamp-2' : 'line-clamp-4',
|
||||
!dataset.embedding_available && 'opacity-50 hover:opacity-100',
|
||||
)}
|
||||
@ -170,15 +170,15 @@ const DatasetCard = ({
|
||||
{dataset.description}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'mt-1 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1',
|
||||
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
|
||||
tags.length ? 'flex' : '!hidden group-hover:!flex',
|
||||
)}>
|
||||
<div className={cn('flex w-0 grow items-center gap-1', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => {
|
||||
<div className={cn('grow flex items-center gap-1 w-0', !dataset.embedding_available && 'opacity-50 hover:opacity-100')} onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}>
|
||||
<div className={cn(
|
||||
'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block',
|
||||
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
|
||||
tags.length ? '!block' : '!hidden',
|
||||
)}>
|
||||
<TagSelector
|
||||
@ -192,26 +192,26 @@ const DatasetCard = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 bg-divider-regular group-hover:!flex' />
|
||||
<div className='!hidden shrink-0 group-hover:!flex'>
|
||||
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px] bg-divider-regular' />
|
||||
<div className='!hidden group-hover:!flex shrink-0'>
|
||||
<CustomPopover
|
||||
htmlContent={<Operations showDelete={!isCurrentWorkspaceDatasetOperator} />}
|
||||
position="br"
|
||||
trigger="click"
|
||||
btnElement={
|
||||
<div
|
||||
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md'
|
||||
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
|
||||
>
|
||||
<RiMoreFill className='h-4 w-4 text-text-secondary' />
|
||||
<RiMoreFill className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
}
|
||||
btnClassName={open =>
|
||||
cn(
|
||||
open ? '!bg-black/5 !shadow-none' : '!bg-transparent',
|
||||
'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5',
|
||||
'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5',
|
||||
)
|
||||
}
|
||||
className={'!z-20 h-fit !w-[128px]'}
|
||||
className={'!w-[128px] h-fit !z-20'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,8 +6,8 @@ const DatasetFooter = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<footer className='shrink-0 grow-0 px-12 py-6'>
|
||||
<h3 className='text-gradient text-xl font-semibold leading-tight'>{t('dataset.didYouKnow')}</h3>
|
||||
<footer className='px-12 py-6 grow-0 shrink-0'>
|
||||
<h3 className='text-xl font-semibold leading-tight text-gradient'>{t('dataset.didYouKnow')}</h3>
|
||||
<p className='mt-1 text-sm font-normal leading-tight text-text-secondary'>
|
||||
{t('dataset.intro1')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro2')}</span>{t('dataset.intro3')}<br />
|
||||
{t('dataset.intro4')}<span className='inline-flex items-center gap-1 text-text-accent'>{t('dataset.intro5')}</span>{t('dataset.intro6')}
|
||||
|
||||
@ -86,7 +86,7 @@ const Datasets = ({
|
||||
}, [onScroll])
|
||||
|
||||
return (
|
||||
<nav className='grid shrink-0 grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0'>
|
||||
{ isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> }
|
||||
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
||||
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
|
||||
|
||||
@ -71,8 +71,8 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
|
||||
<div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
|
||||
{isTocExpanded
|
||||
? (
|
||||
<nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-gray-50 p-4 shadow-md">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<nav className="toc w-full bg-gray-50 p-4 rounded-lg shadow-md max-h-[calc(100vh-150px)] overflow-y-auto">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">{t('appApi.develop.toc')}</h3>
|
||||
<button
|
||||
onClick={() => setIsTocExpanded(false)}
|
||||
@ -86,7 +86,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
|
||||
<li key={index}>
|
||||
<a
|
||||
href={item.href}
|
||||
className="text-gray-600 transition-colors duration-200 hover:text-gray-900 hover:underline"
|
||||
className="text-gray-600 hover:text-gray-900 hover:underline transition-colors duration-200"
|
||||
onClick={e => handleTocClick(e, item)}
|
||||
>
|
||||
{item.text}
|
||||
@ -99,13 +99,13 @@ const Doc = ({ apiBaseUrl }: DocProps) => {
|
||||
: (
|
||||
<button
|
||||
onClick={() => setIsTocExpanded(true)}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 shadow-md transition-colors duration-200 hover:bg-gray-100"
|
||||
className="w-10 h-10 bg-gray-50 rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<RiListUnordered className="h-6 w-6" />
|
||||
<RiListUnordered className="w-6 h-6" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<article className='prose-xl prose mx-1 rounded-t-xl bg-white px-4 pt-16 sm:mx-12'>
|
||||
<article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
|
||||
{locale !== LanguagesSupported[1]
|
||||
? <TemplateEn apiBaseUrl={apiBaseUrl} />
|
||||
: <TemplateZh apiBaseUrl={apiBaseUrl} />
|
||||
|
||||
@ -1,40 +1,37 @@
|
||||
'use client'
|
||||
|
||||
import { forwardRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAddLine,
|
||||
RiArrowRightLine,
|
||||
} from '@remixicon/react'
|
||||
|
||||
const CreateAppCard = (
|
||||
{
|
||||
ref,
|
||||
..._
|
||||
},
|
||||
) => {
|
||||
const CreateAppCard = forwardRef<HTMLAnchorElement>((_, ref) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px]
|
||||
border-components-panel-border transition-all duration-200 ease-in-out'
|
||||
<div className='flex flex-col bg-background-default-dimm border-[0.5px] border-components-panel-border rounded-xl
|
||||
min-h-[160px] transition-all duration-200 ease-in-out'
|
||||
>
|
||||
<a ref={ref} className='group flex grow cursor-pointer items-start p-4' href='/datasets/create'>
|
||||
<a ref={ref} className='group flex flex-grow items-start p-4 cursor-pointer' href='/datasets/create'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter
|
||||
p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
|
||||
<div className='w-10 h-10 p-2 flex items-center justify-center border border-dashed border-divider-regular rounded-lg
|
||||
bg-background-default-lighter group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
|
||||
>
|
||||
<RiAddLine className='h-4 w-4 text-text-tertiary group-hover:text-text-accent'/>
|
||||
<RiAddLine className='w-4 h-4 text-text-tertiary group-hover:text-text-accent'/>
|
||||
</div>
|
||||
<div className='system-md-semibold text-text-secondary group-hover:text-text-accent'>{t('dataset.createDataset')}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div>
|
||||
<a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href='/datasets/connect'>
|
||||
<div className='p-4 pt-0 text-text-tertiary system-xs-regular'>{t('dataset.createDatasetIntro')}</div>
|
||||
<a className='group flex p-4 items-center gap-1 border-t-[0.5px] border-divider-subtle rounded-b-xl cursor-pointer' href='/datasets/connect'>
|
||||
<div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div>
|
||||
<RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' />
|
||||
<RiArrowRightLine className='w-3.5 h-3.5 text-text-tertiary group-hover:text-text-accent' />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
CreateAppCard.displayName = 'CreateAppCard'
|
||||
|
||||
|
||||
@ -961,12 +961,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
<Property name='status' type='string' key='status'>
|
||||
Search status, completed
|
||||
</Property>
|
||||
<Property name='page' type='string' key='page'>
|
||||
Page number (optional)
|
||||
</Property>
|
||||
<Property name='limit' type='string' key='limit'>
|
||||
Number of items returned, default 20, range 1-100 (optional)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
@ -1010,11 +1004,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"doc_form": "text_model",
|
||||
"has_more": false,
|
||||
"limit": 20,
|
||||
"total": 9,
|
||||
"page": 1
|
||||
"doc_form": "text_model"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1158,276 +1148,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||
method='POST'
|
||||
title='Create Child Chunk'
|
||||
name='#create_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Params
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Knowledge ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
Document ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
Segment ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='content' type='string' key='content'>
|
||||
Child chunk content
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "Child chunk content"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"content": "Child chunk content"
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": {
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "Child chunk content",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||
method='GET'
|
||||
title='Get Child Chunks'
|
||||
name='#get_child_chunks'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Params
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Knowledge ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
Document ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
Segment ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Query
|
||||
<Properties>
|
||||
<Property name='keyword' type='string' key='keyword'>
|
||||
Search keyword (optional)
|
||||
</Property>
|
||||
<Property name='page' type='integer' key='page'>
|
||||
Page number (optional, default: 1)
|
||||
</Property>
|
||||
<Property name='limit' type='integer' key='limit'>
|
||||
Items per page (optional, default: 20, max: 100)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="GET"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \\\n--header 'Authorization: Bearer {api_key}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": [{
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "Child chunk content",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"total": 1,
|
||||
"total_pages": 1,
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||
method='DELETE'
|
||||
title='Delete Child Chunk'
|
||||
name='#delete_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Params
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Knowledge ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
Document ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
Segment ID
|
||||
</Property>
|
||||
<Property name='child_chunk_id' type='string' key='child_chunk_id'>
|
||||
Child Chunk ID
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="DELETE"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}"
|
||||
targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||
method='PATCH'
|
||||
title='Update Child Chunk'
|
||||
name='#update_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Params
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Knowledge ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
Document ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
Segment ID
|
||||
</Property>
|
||||
<Property name='child_chunk_id' type='string' key='child_chunk_id'>
|
||||
Child Chunk ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='content' type='string' key='content'>
|
||||
Child chunk content
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="PATCH"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}"
|
||||
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "Updated child chunk content"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"content": "Updated child chunk content"
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": {
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "Updated child chunk content",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||
method='GET'
|
||||
@ -1974,4 +1694,4 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="pb-4" />
|
||||
<div className="pb-4" />
|
||||
|
||||
@ -961,12 +961,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
<Property name='status' type='string' key='status'>
|
||||
搜索状态,completed
|
||||
</Property>
|
||||
<Property name='page' type='string' key='page'>
|
||||
页码,可选
|
||||
</Property>
|
||||
<Property name='limit' type='string' key='limit'>
|
||||
返回条数,可选,默认 20,范围 1-100
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
@ -1010,11 +1004,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"doc_form": "text_model",
|
||||
"has_more": false,
|
||||
"limit": 20,
|
||||
"total": 9,
|
||||
"page": 1
|
||||
"doc_form": "text_model"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@ -1159,310 +1149,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||
method='POST'
|
||||
title='新增文档子分段'
|
||||
name='#create_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
文档 ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
分段 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='content' type='string' key='content'>
|
||||
子分段内容
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks"
|
||||
targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "子分段内容"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"content": "子分段内容"
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": {
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "子分段内容",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks'
|
||||
method='GET'
|
||||
title='查询文档子分段'
|
||||
name='#get_child_chunks'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
文档 ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
分段 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Query
|
||||
<Properties>
|
||||
<Property name='keyword' type='string' key='keyword'>
|
||||
搜索关键词(选填)
|
||||
</Property>
|
||||
<Property name='page' type='integer' key='page'>
|
||||
页码(选填,默认1)
|
||||
</Property>
|
||||
<Property name='limit' type='integer' key='limit'>
|
||||
每页数量(选填,默认20,最大100)
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="GET"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \\\n--header 'Authorization: Bearer {api_key}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks?page=1&limit=20' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": [{
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "子分段内容",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"total": 1,
|
||||
"total_pages": 1,
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||
method='DELETE'
|
||||
title='删除文档子分段'
|
||||
name='#delete_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
文档 ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
分段 ID
|
||||
</Property>
|
||||
<Property name='child_chunk_id' type='string' key='child_chunk_id'>
|
||||
子分段 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="DELETE"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}"
|
||||
targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request DELETE '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \
|
||||
--header 'Authorization: Bearer {api_key}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
### 错误信息
|
||||
<Properties>
|
||||
<Property name='code' type='string' key='code'>
|
||||
返回的错误代码
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name='status' type='number' key='status'>
|
||||
返回的错误状态
|
||||
</Property>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<Property name='message' type='string' key='message'>
|
||||
返回的错误信息
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col>
|
||||
<CodeGroup title="Example">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"code": "no_file_uploaded",
|
||||
"message": "Please upload your file.",
|
||||
"status": 400
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}'
|
||||
method='PATCH'
|
||||
title='更新文档子分段'
|
||||
name='#update_child_chunk'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
<Property name='document_id' type='string' key='document_id'>
|
||||
文档 ID
|
||||
</Property>
|
||||
<Property name='segment_id' type='string' key='segment_id'>
|
||||
分段 ID
|
||||
</Property>
|
||||
<Property name='child_chunk_id' type='string' key='child_chunk_id'>
|
||||
子分段 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='content' type='string' key='content'>
|
||||
子分段内容
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="PATCH"
|
||||
label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}"
|
||||
targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"content": "更新的子分段内容"}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request PATCH '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id}' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"content": "更新的子分段内容"
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"data": {
|
||||
"id": "",
|
||||
"segment_id": "",
|
||||
"content": "更新的子分段内容",
|
||||
"word_count": 25,
|
||||
"tokens": 0,
|
||||
"index_node_id": "",
|
||||
"index_node_hash": "",
|
||||
"status": "completed",
|
||||
"created_by": "",
|
||||
"created_at": 1695312007,
|
||||
"indexing_at": 1695312007,
|
||||
"completed_at": 1695312007,
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<hr className='ml-0 mr-0' />
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/documents/{document_id}/upload-file'
|
||||
method='GET'
|
||||
|
||||
@ -3,14 +3,14 @@ import React from 'react'
|
||||
import Main from '@/app/components/explore/installed-app'
|
||||
|
||||
export type IInstalledAppProps = {
|
||||
params: Promise<{
|
||||
params: {
|
||||
appId: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
const InstalledApp: FC<IInstalledAppProps> = async ({ params }) => {
|
||||
const InstalledApp: FC<IInstalledAppProps> = ({ params: { appId } }) => {
|
||||
return (
|
||||
<Main id={(await params).appId} />
|
||||
<Main id={appId} />
|
||||
)
|
||||
}
|
||||
export default React.memo(InstalledApp)
|
||||
|
||||
@ -10,7 +10,7 @@ const Layout: FC<{
|
||||
children: React.ReactNode
|
||||
}> = ({ children }) => {
|
||||
return (
|
||||
<div className="h-full min-w-[300px] pb-[env(safe-area-inset-bottom)]">
|
||||
<div className="min-w-[300px] h-full pb-[env(safe-area-inset-bottom)]">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -92,8 +92,8 @@ const WebSSOForm: FC = () => {
|
||||
}, [message, tokenFromUrl]) // Added dependencies to useEffect
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className={cn('flex w-full grow flex-col items-center justify-center', 'px-6', 'md:px-[108px]')}>
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className={cn('flex flex-col items-center w-full grow justify-center', 'px-6', 'md:px-[108px]')}>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -83,13 +83,13 @@ const AvatarWithEdit = ({ onSave, ...props }: AvatarWithEditProps) => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="group relative">
|
||||
<div className="relative group">
|
||||
<Avatar {...props} />
|
||||
<div
|
||||
onClick={() => { setIsShowAvatarPicker(true) }}
|
||||
className="absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
className="absolute inset-0 bg-black bg-opacity-50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer flex items-center justify-center"
|
||||
>
|
||||
<span className="text-xs text-white">
|
||||
<span className="text-white text-xs">
|
||||
<RiPencilLine />
|
||||
</span>
|
||||
</div>
|
||||
@ -105,7 +105,7 @@ const AvatarWithEdit = ({ onSave, ...props }: AvatarWithEditProps) => {
|
||||
<ImageInput onImageInput={handleImageInput} cropShape='round' />
|
||||
<Divider className='m-0' />
|
||||
|
||||
<div className='flex w-full items-center justify-center gap-2 p-3'>
|
||||
<div className='w-full flex items-center justify-center p-3 gap-2'>
|
||||
<Button className='w-full' onClick={() => setIsShowAvatarPicker(false)}>
|
||||
{t('app.iconPicker.cancel')}
|
||||
</Button>
|
||||
|
||||
@ -122,17 +122,17 @@ export default function AccountPage() {
|
||||
<div className='mr-3'>
|
||||
<AppIcon size='tiny' />
|
||||
</div>
|
||||
<div className='system-sm-medium mt-[3px] text-text-secondary'>{item.name}</div>
|
||||
<div className='mt-[3px] system-sm-medium text-text-secondary'>{item.name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='pb-3 pt-2'>
|
||||
<div className='pt-2 pb-3'>
|
||||
<h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4>
|
||||
</div>
|
||||
<div className='mb-8 flex items-center rounded-xl bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-6'>
|
||||
<div className='mb-8 p-6 rounded-xl flex items-center bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1'>
|
||||
<AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={ mutateUserProfile } size={64} />
|
||||
<div className='ml-4'>
|
||||
<p className='system-xl-semibold text-text-primary'>{userProfile.name}</p>
|
||||
@ -141,19 +141,19 @@ export default function AccountPage() {
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
<div className={titleClassName}>{t('common.account.name')}</div>
|
||||
<div className='mt-2 flex w-full items-center justify-between gap-2'>
|
||||
<div className='system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled '>
|
||||
<div className='flex items-center justify-between gap-2 w-full mt-2'>
|
||||
<div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<span className='pl-1'>{userProfile.name}</span>
|
||||
</div>
|
||||
<div className='system-sm-medium cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text' onClick={handleEditName}>
|
||||
<div className='bg-components-button-tertiary-bg rounded-lg py-2 px-3 cursor-pointer system-sm-medium text-components-button-tertiary-text' onClick={handleEditName}>
|
||||
{t('common.operation.edit')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
<div className={titleClassName}>{t('common.account.email')}</div>
|
||||
<div className='mt-2 flex w-full items-center justify-between gap-2'>
|
||||
<div className='system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled '>
|
||||
<div className='flex items-center justify-between gap-2 w-full mt-2'>
|
||||
<div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<span className='pl-1'>{userProfile.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,8 +162,8 @@ export default function AccountPage() {
|
||||
systemFeatures.enable_email_password_login && (
|
||||
<div className='mb-8 flex justify-between gap-2'>
|
||||
<div>
|
||||
<div className='system-sm-semibold mb-1 text-text-secondary'>{t('common.account.password')}</div>
|
||||
<div className='body-xs-regular mb-2 text-text-tertiary'>{t('common.account.passwordTip')}</div>
|
||||
<div className='mb-1 system-sm-semibold text-text-secondary'>{t('common.account.password')}</div>
|
||||
<div className='mb-2 body-xs-regular text-text-tertiary'>{t('common.account.passwordTip')}</div>
|
||||
</div>
|
||||
<Button onClick={() => setEditPasswordModalVisible(true)}>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</Button>
|
||||
</div>
|
||||
@ -190,13 +190,13 @@ export default function AccountPage() {
|
||||
onClose={() => setEditNameModalVisible(false)}
|
||||
className={s.modal}
|
||||
>
|
||||
<div className='title-2xl-semi-bold mb-6 text-text-primary'>{t('common.account.editName')}</div>
|
||||
<div className='mb-6 title-2xl-semi-bold text-text-primary'>{t('common.account.editName')}</div>
|
||||
<div className={titleClassName}>{t('common.account.name')}</div>
|
||||
<Input className='mt-2'
|
||||
value={editName}
|
||||
onChange={e => setEditName(e.target.value)}
|
||||
/>
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<div className='flex justify-end mt-10'>
|
||||
<Button className='mr-2' onClick={() => setEditNameModalVisible(false)}>{t('common.operation.cancel')}</Button>
|
||||
<Button
|
||||
disabled={editing || !editName}
|
||||
@ -219,7 +219,7 @@ export default function AccountPage() {
|
||||
}}
|
||||
className={s.modal}
|
||||
>
|
||||
<div className='title-2xl-semi-bold mb-6 text-text-primary'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div>
|
||||
<div className='mb-6 title-2xl-semi-bold text-text-primary'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div>
|
||||
{userProfile.is_password_set && (
|
||||
<>
|
||||
<div className={titleClassName}>{t('common.account.currentPassword')}</div>
|
||||
@ -242,7 +242,7 @@ export default function AccountPage() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className='system-sm-semibold mt-8 text-text-secondary'>
|
||||
<div className='mt-8 system-sm-semibold text-text-secondary'>
|
||||
{userProfile.is_password_set ? t('common.account.newPassword') : t('common.account.password')}
|
||||
</div>
|
||||
<div className='relative mt-2'>
|
||||
@ -261,7 +261,7 @@ export default function AccountPage() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='system-sm-semibold mt-8 text-text-secondary'>{t('common.account.confirmPassword')}</div>
|
||||
<div className='mt-8 system-sm-semibold text-text-secondary'>{t('common.account.confirmPassword')}</div>
|
||||
<div className='relative mt-2'>
|
||||
<Input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
@ -278,7 +278,7 @@ export default function AccountPage() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<div className='flex justify-end mt-10'>
|
||||
<Button className='mr-2' onClick={() => {
|
||||
setEditPasswordModalVisible(false)
|
||||
resetPasswordForm()
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Fragment } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
import { logout } from '@/service/common'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
export type IAppSelector = {
|
||||
export interface IAppSelector {
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
@ -36,17 +36,17 @@ export default function AppSelector() {
|
||||
({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<MenuButton
|
||||
<Menu.Button
|
||||
className={`
|
||||
p-1x inline-flex
|
||||
items-center rounded-[20px] text-sm
|
||||
inline-flex items-center
|
||||
rounded-[20px] p-1x text-sm
|
||||
text-text-primary
|
||||
mobile:px-1
|
||||
${open && 'bg-components-panel-bg-blur'}
|
||||
`}
|
||||
>
|
||||
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={32} />
|
||||
</MenuButton>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
@ -57,35 +57,35 @@ export default function AppSelector() {
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
<Menu.Items
|
||||
className="
|
||||
absolute -right-2 -top-1 w-60 max-w-80
|
||||
origin-top-right divide-y divide-divider-subtle rounded-lg bg-components-panel-bg-blur
|
||||
divide-y divide-divider-subtle origin-top-right rounded-lg bg-components-panel-bg-blur
|
||||
shadow-lg
|
||||
"
|
||||
>
|
||||
<MenuItem>
|
||||
<Menu.Item>
|
||||
<div className='p-1'>
|
||||
<div className='flex flex-nowrap items-center px-3 py-2'>
|
||||
<div className='grow'>
|
||||
<div className='system-md-medium break-all text-text-primary'>{userProfile.name}</div>
|
||||
<div className='system-xs-regular break-all text-text-tertiary'>{userProfile.email}</div>
|
||||
<div className='system-md-medium text-text-primary break-all'>{userProfile.name}</div>
|
||||
<div className='system-xs-regular text-text-tertiary break-all'>{userProfile.email}</div>
|
||||
</div>
|
||||
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<div className='p-1' onClick={() => handleLogout()}>
|
||||
<div
|
||||
className='group flex h-9 cursor-pointer items-center justify-start rounded-lg px-3 hover:bg-state-base-hover'
|
||||
className='flex items-center justify-start h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover'
|
||||
>
|
||||
<LogOut01 className='mr-1 flex h-4 w-4 text-text-tertiary' />
|
||||
<div className='text-[14px] font-normal text-text-secondary'>{t('common.userProfile.logout')}</div>
|
||||
<LogOut01 className='w-4 h-4 text-text-tertiary flex mr-1' />
|
||||
<div className='font-normal text-[14px] text-text-secondary'>{t('common.userProfile.logout')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -29,18 +29,18 @@ export default function CheckEmail(props: DeleteAccountProps) {
|
||||
}, [getDeleteEmailVerifyCode, props])
|
||||
|
||||
return <>
|
||||
<div className='body-md-medium py-1 text-text-destructive'>
|
||||
<div className='py-1 text-text-destructive body-md-medium'>
|
||||
{t('common.account.deleteTip')}
|
||||
</div>
|
||||
<div className='body-md-regular pb-2 pt-1 text-text-secondary'>
|
||||
<div className='pt-1 pb-2 text-text-secondary body-md-regular'>
|
||||
{t('common.account.deletePrivacyLinkTip')}
|
||||
<Link href='https://dify.ai/privacy' className='text-text-accent'>{t('common.account.deletePrivacyLink')}</Link>
|
||||
</div>
|
||||
<label className='system-sm-semibold mb-1 mt-3 flex h-6 items-center text-text-secondary'>{t('common.account.deleteLabel')}</label>
|
||||
<label className='mt-3 mb-1 h-6 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.deleteLabel')}</label>
|
||||
<Input placeholder={t('common.account.deletePlaceholder') as string} onChange={(e) => {
|
||||
setUserInputEmail(e.target.value)
|
||||
}} />
|
||||
<div className='mt-3 flex w-full flex-col gap-2'>
|
||||
<div className='w-full flex flex-col mt-3 gap-2'>
|
||||
<Button className='w-full' disabled={userInputEmail !== userProfile.email || isSendingEmail} loading={isSendingEmail} variant='primary' onClick={handleConfirm}>{t('common.account.sendVerificationButton')}</Button>
|
||||
<Button className='w-full' onClick={props.onCancel}>{t('common.operation.cancel')}</Button>
|
||||
</div>
|
||||
|
||||
@ -56,11 +56,11 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
className="max-w-[480px]"
|
||||
footer={false}
|
||||
>
|
||||
<label className='system-sm-semibold mb-1 mt-3 flex items-center text-text-secondary'>{t('common.account.feedbackLabel')}</label>
|
||||
<label className='mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.feedbackLabel')}</label>
|
||||
<Textarea rows={6} value={userFeedback} placeholder={t('common.account.feedbackPlaceholder') as string} onChange={(e) => {
|
||||
setUserFeedback(e.target.value)
|
||||
}} />
|
||||
<div className='mt-3 flex w-full flex-col gap-2'>
|
||||
<div className='w-full flex flex-col mt-3 gap-2'>
|
||||
<Button className='w-full' loading={isPending} variant='primary' onClick={handleSubmit}>{t('common.operation.submit')}</Button>
|
||||
<Button className='w-full' onClick={handleSkip}>{t('common.operation.skip')}</Button>
|
||||
</div>
|
||||
|
||||
@ -35,18 +35,18 @@ export default function VerifyEmail(props: DeleteAccountProps) {
|
||||
catch (error) { console.error(error) }
|
||||
}, [emailToken, verificationCode, confirmDeleteAccount, props])
|
||||
return <>
|
||||
<div className='body-md-medium pt-1 text-text-destructive'>
|
||||
<div className='pt-1 text-text-destructive body-md-medium'>
|
||||
{t('common.account.deleteTip')}
|
||||
</div>
|
||||
<div className='body-md-regular pb-2 pt-1 text-text-secondary'>
|
||||
<div className='pt-1 pb-2 text-text-secondary body-md-regular'>
|
||||
{t('common.account.deletePrivacyLinkTip')}
|
||||
<Link href='https://dify.ai/privacy' className='text-text-accent'>{t('common.account.deletePrivacyLink')}</Link>
|
||||
</div>
|
||||
<label className='system-sm-semibold mb-1 mt-3 flex h-6 items-center text-text-secondary'>{t('common.account.verificationLabel')}</label>
|
||||
<label className='mt-3 mb-1 h-6 flex items-center system-sm-semibold text-text-secondary'>{t('common.account.verificationLabel')}</label>
|
||||
<Input minLength={6} maxLength={6} placeholder={t('common.account.verificationPlaceholder') as string} onChange={(e) => {
|
||||
setVerificationCode(e.target.value)
|
||||
}} />
|
||||
<div className='mt-3 flex w-full flex-col gap-2'>
|
||||
<div className='w-full flex flex-col mt-3 gap-2'>
|
||||
<Button className='w-full' disabled={shouldButtonDisabled} loading={isDeleting} variant='warning' onClick={handleConfirm}>{t('common.account.permanentlyDeleteButton')}</Button>
|
||||
<Button className='w-full' onClick={props.onCancel}>{t('common.operation.cancel')}</Button>
|
||||
<Countdown onResend={sendEmail} />
|
||||
|
||||
@ -16,19 +16,19 @@ const Header = () => {
|
||||
return (
|
||||
<div className='flex flex-1 items-center justify-between px-4'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex cursor-pointer items-center' onClick={back}>
|
||||
<div className='flex items-center cursor-pointer' onClick={back}>
|
||||
<LogoSite className='object-contain' />
|
||||
</div>
|
||||
<div className='h-4 w-[1px] bg-divider-regular' />
|
||||
<p className='title-3xl-semi-bold text-text-primary'>{t('common.account.account')}</p>
|
||||
<div className='w-[1px] h-4 bg-divider-regular' />
|
||||
<p className='text-text-primary title-3xl-semi-bold'>{t('common.account.account')}</p>
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center gap-3'>
|
||||
<Button className='system-sm-medium gap-2 px-3 py-2' onClick={back}>
|
||||
<RiRobot2Line className='h-4 w-4' />
|
||||
<div className='flex items-center flex-shrink-0 gap-3'>
|
||||
<Button className='gap-2 py-2 px-3 system-sm-medium' onClick={back}>
|
||||
<RiRobot2Line className='w-4 h-4' />
|
||||
<p>{t('common.account.studio')}</p>
|
||||
<RiArrowRightUpLine className='h-4 w-4' />
|
||||
<RiArrowRightUpLine className='w-4 h-4' />
|
||||
</Button>
|
||||
<div className='h-4 w-[1px] bg-divider-regular' />
|
||||
<div className='w-[1px] h-4 bg-divider-regular' />
|
||||
<Avatar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
<div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-components-panel-bg'>
|
||||
<div className='relative flex flex-col overflow-y-auto bg-components-panel-bg shrink-0 h-0 grow'>
|
||||
{children}
|
||||
</div>
|
||||
</ModalContextProvider>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import AccountPage from './account-page'
|
||||
|
||||
export default function Account() {
|
||||
return <div className='mx-auto w-full max-w-[640px] px-6 pt-12'>
|
||||
return <div className='max-w-[640px] w-full mx-auto pt-12 px-6'>
|
||||
<AccountPage />
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ const ActivateForm = () => {
|
||||
return (
|
||||
<div className={
|
||||
cn(
|
||||
'flex w-full grow flex-col items-center justify-center',
|
||||
'flex flex-col items-center w-full grow justify-center',
|
||||
'px-6',
|
||||
'md:px-[108px]',
|
||||
)
|
||||
@ -49,11 +49,11 @@ const ActivateForm = () => {
|
||||
{!checkRes && <Loading />}
|
||||
{checkRes && !checkRes.is_valid && (
|
||||
<div className="flex flex-col md:w-[400px]">
|
||||
<div className="mx-auto w-full">
|
||||
<div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-gray-100 p-5 text-[40px] font-bold shadow-lg">🤷♂️</div>
|
||||
<div className="w-full mx-auto">
|
||||
<div className="mb-3 flex justify-center items-center w-20 h-20 p-5 rounded-[20px] border border-gray-100 shadow-lg text-[40px] font-bold">🤷♂️</div>
|
||||
<h2 className="text-[32px] font-bold text-gray-900">{t('login.invalid')}</h2>
|
||||
</div>
|
||||
<div className="mx-auto mt-6 w-full">
|
||||
<div className="w-full mx-auto mt-6">
|
||||
<Button variant='primary' className='w-full !text-sm'>
|
||||
<a href="https://dify.ai">{t('login.explore')}</a>
|
||||
</Button>
|
||||
|
||||
@ -8,14 +8,14 @@ const Activate = () => {
|
||||
return (
|
||||
<div className={cn(
|
||||
style.background,
|
||||
'flex min-h-screen w-full',
|
||||
'flex w-full min-h-screen',
|
||||
'sm:p-4 lg:p-8',
|
||||
'gap-x-20',
|
||||
'justify-center lg:justify-start',
|
||||
)}>
|
||||
<div className={
|
||||
cn(
|
||||
'flex w-full shrink-0 flex-col rounded-2xl bg-white shadow',
|
||||
'flex w-full flex-col bg-white shadow rounded-2xl shrink-0',
|
||||
'space-between',
|
||||
)
|
||||
}>
|
||||
|
||||
@ -191,7 +191,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
}}
|
||||
className='block w-full'
|
||||
>
|
||||
<div className={cn('flex rounded-lg', expand ? 'flex-col gap-2 p-2 pb-2.5' : 'items-start justify-center gap-1 p-1', open && 'bg-state-base-hover', isCurrentWorkspaceEditor && 'cursor-pointer hover:bg-state-base-hover')}>
|
||||
<div className={cn('flex rounded-lg', expand ? 'p-2 pb-2.5 flex-col gap-2' : 'p-1 gap-1 justify-center items-start', open && 'bg-state-base-hover', isCurrentWorkspaceEditor && 'hover:bg-state-base-hover cursor-pointer')}>
|
||||
<div className={`flex items-center self-stretch ${expand ? 'justify-between' : 'flex-col gap-1'}`}>
|
||||
<AppIcon
|
||||
size={expand ? 'large' : 'small'}
|
||||
@ -200,9 +200,9 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
background={appDetail.icon_background}
|
||||
imageUrl={appDetail.icon_url}
|
||||
/>
|
||||
<div className='flex items-center justify-center rounded-md p-0.5'>
|
||||
<div className='flex h-5 w-5 items-center justify-center'>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<div className='flex p-0.5 justify-center items-center rounded-md'>
|
||||
<div className='flex w-5 h-5 justify-center items-center'>
|
||||
<RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -210,9 +210,9 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
expand && (
|
||||
<div className='flex flex-col items-start gap-1'>
|
||||
<div className='flex w-full'>
|
||||
<div className='system-md-semibold truncate text-text-secondary'>{appDetail.name}</div>
|
||||
<div className='text-text-secondary system-md-semibold truncate'>{appDetail.name}</div>
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -221,9 +221,9 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
<ContentDialog
|
||||
show={open}
|
||||
onClose={() => setOpen(false)}
|
||||
className='absolute bottom-2 left-2 top-2 flex w-[420px] flex-col rounded-2xl !p-0'
|
||||
className='!p-0 flex flex-col absolute left-2 top-2 bottom-2 w-[420px] rounded-2xl'
|
||||
>
|
||||
<div className='flex shrink-0 flex-col items-start justify-center gap-3 self-stretch p-4'>
|
||||
<div className='flex p-4 flex-col justify-center items-start gap-3 self-stretch shrink-0'>
|
||||
<div className='flex items-center gap-3 self-stretch'>
|
||||
<AppIcon
|
||||
size="large"
|
||||
@ -232,17 +232,17 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
background={appDetail.icon_background}
|
||||
imageUrl={appDetail.icon_url}
|
||||
/>
|
||||
<div className='flex w-full grow flex-col items-start justify-center'>
|
||||
<div className='system-md-semibold w-full truncate text-text-secondary'>{appDetail.name}</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{appDetail.mode === 'advanced-chat' ? t('app.types.advanced') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||
<div className='flex flex-col justify-center items-start grow w-full'>
|
||||
<div className='text-text-secondary system-md-semibold truncate w-full'>{appDetail.name}</div>
|
||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* description */}
|
||||
{appDetail.description && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{appDetail.description}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{appDetail.description}</div>
|
||||
)}
|
||||
{/* operations */}
|
||||
<div className='flex flex-wrap items-center gap-1 self-stretch'>
|
||||
<div className='flex items-center gap-1 self-stretch'>
|
||||
<Button
|
||||
size={'small'}
|
||||
variant={'secondary'}
|
||||
@ -252,8 +252,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
setShowEditModal(true)
|
||||
}}
|
||||
>
|
||||
<RiEditLine className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
||||
<span className='system-xs-medium text-components-button-secondary-text'>{t('app.editApp')}</span>
|
||||
<RiEditLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
|
||||
<span className='text-components-button-secondary-text system-xs-medium'>{t('app.editApp')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
@ -264,8 +264,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
setShowDuplicateModal(true)
|
||||
}}
|
||||
>
|
||||
<RiFileCopy2Line className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
||||
<span className='system-xs-medium text-components-button-secondary-text'>{t('app.duplicate')}</span>
|
||||
<RiFileCopy2Line className='w-3.5 h-3.5 text-components-button-secondary-text' />
|
||||
<span className='text-components-button-secondary-text system-xs-medium'>{t('app.duplicate')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
@ -273,8 +273,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
className='gap-[1px]'
|
||||
onClick={exportCheck}
|
||||
>
|
||||
<RiFileDownloadLine className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
||||
<span className='system-xs-medium text-components-button-secondary-text'>{t('app.export')}</span>
|
||||
<RiFileDownloadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
|
||||
<span className='text-components-button-secondary-text system-xs-medium'>{t('app.export')}</span>
|
||||
</Button>
|
||||
{
|
||||
(appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (
|
||||
@ -287,8 +287,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
setShowImportDSLModal(true)
|
||||
}}
|
||||
>
|
||||
<RiFileUploadLine className='h-3.5 w-3.5 text-components-button-secondary-text' />
|
||||
<span className='system-xs-medium text-components-button-secondary-text'>{t('workflow.common.importDSL')}</span>
|
||||
<RiFileUploadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
|
||||
<span className='text-components-button-secondary-text system-xs-medium'>{t('workflow.common.importDSL')}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@ -298,10 +298,10 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
<CardView
|
||||
appId={appDetail.id}
|
||||
isInPanel={true}
|
||||
className='flex grow flex-col gap-2 overflow-auto px-2 py-1'
|
||||
className='flex flex-col px-2 py-1 gap-2 grow overflow-auto'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex min-h-fit shrink-0 flex-col items-start justify-center gap-3 self-stretch border-t-[0.5px] border-divider-subtle p-2'>
|
||||
<div className='flex p-2 flex-col justify-center items-start gap-3 self-stretch border-t-[0.5px] border-divider-subtle shrink-0 min-h-fit'>
|
||||
<Button
|
||||
size={'medium'}
|
||||
variant={'ghost'}
|
||||
@ -311,8 +311,8 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
setShowConfirmDelete(true)
|
||||
}}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||
<span className='system-sm-medium text-text-tertiary'>{t('common.operation.deleteApp')}</span>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
|
||||
<span className='text-text-tertiary system-sm-medium'>{t('common.operation.deleteApp')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</ContentDialog>
|
||||
|
||||
@ -48,9 +48,9 @@ const NotionSvg = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xm
|
||||
|
||||
const ICON_MAP = {
|
||||
app: <AppIcon className='border !border-[rgba(0,0,0,0.05)]' />,
|
||||
api: <AppIcon innerIcon={ApiSvg} className='border !border-purple-200 !bg-purple-50' />,
|
||||
api: <AppIcon innerIcon={ApiSvg} className='border !bg-purple-50 !border-purple-200' />,
|
||||
dataset: <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />,
|
||||
webapp: <AppIcon innerIcon={WebappSvg} className='border !border-primary-200 !bg-primary-100' />,
|
||||
webapp: <AppIcon innerIcon={WebappSvg} className='border !bg-primary-100 !border-primary-200' />,
|
||||
notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />,
|
||||
}
|
||||
|
||||
@ -58,20 +58,20 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex grow items-center">
|
||||
<div className="flex items-center grow">
|
||||
{icon && icon_background && iconType === 'app' && (
|
||||
<div className='mr-3 shrink-0'>
|
||||
<div className='shrink-0 mr-3'>
|
||||
<AppIcon icon={icon} background={icon_background} />
|
||||
</div>
|
||||
)}
|
||||
{iconType !== 'app'
|
||||
&& <div className='mr-3 shrink-0'>
|
||||
&& <div className='shrink-0 mr-3'>
|
||||
{ICON_MAP[iconType]}
|
||||
</div>
|
||||
|
||||
}
|
||||
{mode === 'expand' && <div className="group">
|
||||
<div className={`system-md-semibold flex flex-row items-center text-text-secondary group-hover:text-text-primary ${textStyle?.main ?? ''}`}>
|
||||
<div className={`flex flex-row items-center system-md-semibold text-text-secondary group-hover:text-text-primary ${textStyle?.main ?? ''}`}>
|
||||
<div className="max-w-[180px] truncate">
|
||||
{name}
|
||||
</div>
|
||||
@ -88,7 +88,7 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{isExternal ? t('dataset.externalTag') : ''}</div>
|
||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : ''}</div>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -26,7 +26,7 @@ const DatasetInfo: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='pl-1 pt-1'>
|
||||
<div className='mr-3 shrink-0'>
|
||||
<div className='flex-shrink-0 mr-3'>
|
||||
<AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />
|
||||
</div>
|
||||
{expand && (
|
||||
@ -34,8 +34,8 @@ const DatasetInfo: FC<Props> = ({
|
||||
<div className='system-md-semibold text-text-secondary'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase mt-1 text-text-tertiary'>{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}</div>
|
||||
<div className='system-xs-regular my-3 text-text-tertiary first-letter:capitalize'>{description}</div>
|
||||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}</div>
|
||||
<div className='my-3 system-xs-regular text-text-tertiary first-letter:capitalize'>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
{extraInfo}
|
||||
|
||||
@ -50,7 +50,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all
|
||||
shrink-0 flex flex-col bg-background-default-subtle border-r border-divider-burn transition-all
|
||||
${expand ? 'w-[216px]' : 'w-14'}
|
||||
`}
|
||||
>
|
||||
@ -85,7 +85,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
|
||||
)}
|
||||
</div>
|
||||
<div className='px-4'>
|
||||
<div className={cn('mx-auto mt-1 h-[1px] bg-divider-subtle', !expand && 'w-6')} />
|
||||
<div className={cn('mt-1 mx-auto h-[1px] bg-divider-subtle', !expand && 'w-6')} />
|
||||
</div>
|
||||
<nav
|
||||
className={`
|
||||
@ -108,13 +108,13 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className='flex h-6 w-6 cursor-pointer items-center justify-center text-gray-500'
|
||||
className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer'
|
||||
onClick={() => handleToggle(appSidebarExpand)}
|
||||
>
|
||||
{
|
||||
expand
|
||||
? <RiLayoutRight2Line className='h-5 w-5 text-components-menu-item-text' />
|
||||
: <LayoutRight2LineMod className='h-5 w-5 text-components-menu-item-text' />
|
||||
? <RiLayoutRight2Line className='w-5 h-5 text-components-menu-item-text' />
|
||||
: <LayoutRight2LineMod className='w-5 h-5 text-components-menu-item-text' />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,17 +21,17 @@ const EditItem: FC<Props> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const avatar = type === EditItemType.Query ? <User className='h-6 w-6' /> : <Robot className='h-6 w-6' />
|
||||
const avatar = type === EditItemType.Query ? <User className='w-6 h-6' /> : <Robot className='w-6 h-6' />
|
||||
const name = type === EditItemType.Query ? t('appAnnotation.addModal.queryName') : t('appAnnotation.addModal.answerName')
|
||||
const placeholder = type === EditItemType.Query ? t('appAnnotation.addModal.queryPlaceholder') : t('appAnnotation.addModal.answerPlaceholder')
|
||||
|
||||
return (
|
||||
<div className='flex' onClick={e => e.stopPropagation()}>
|
||||
<div className='mr-3 shrink-0'>
|
||||
<div className='shrink-0 mr-3'>
|
||||
{avatar}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='system-xs-semibold mb-1 text-text-primary'>{name}</div>
|
||||
<div className='mb-1 system-xs-semibold text-text-primary'>{name}</div>
|
||||
<Textarea
|
||||
value={content}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}
|
||||
|
||||
@ -76,7 +76,7 @@ const AddAnnotationModal: FC<Props> = ({
|
||||
maxWidthClassName='!max-w-[480px]'
|
||||
title={t('appAnnotation.addModal.title') as string}
|
||||
body={(
|
||||
<div className='space-y-6 p-6 pb-4'>
|
||||
<div className='p-6 pb-4 space-y-6'>
|
||||
<EditItem
|
||||
type={EditItemType.Query}
|
||||
content={question}
|
||||
@ -93,11 +93,11 @@ const AddAnnotationModal: FC<Props> = ({
|
||||
(
|
||||
<div>
|
||||
{isAnnotationFull && (
|
||||
<div className='mb-4 mt-6 px-6'>
|
||||
<div className='mt-6 mb-4 px-6'>
|
||||
<AnnotationFull />
|
||||
</div>
|
||||
)}
|
||||
<div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'>
|
||||
<div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'>
|
||||
<div
|
||||
className='flex items-center space-x-2'
|
||||
>
|
||||
|
||||
@ -35,17 +35,17 @@ const CSVDownload: FC = () => {
|
||||
<div className='mt-6'>
|
||||
<div className='system-sm-medium text-text-primary'>{t('share.generation.csvStructureTitle')}</div>
|
||||
<div className='mt-2 max-h-[500px] overflow-auto'>
|
||||
<table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-regular text-xs'>
|
||||
<table className='table-fixed w-full border-separate border-spacing-0 border border-divider-regular rounded-lg text-xs'>
|
||||
<thead className='text-text-tertiary'>
|
||||
<tr>
|
||||
<td className='h-9 border-b border-divider-regular pl-3 pr-2'>{t('appAnnotation.batchModal.question')}</td>
|
||||
<td className='h-9 border-b border-divider-regular pl-3 pr-2'>{t('appAnnotation.batchModal.answer')}</td>
|
||||
<td className='h-9 pl-3 pr-2 border-b border-divider-regular'>{t('appAnnotation.batchModal.question')}</td>
|
||||
<td className='h-9 pl-3 pr-2 border-b border-divider-regular'>{t('appAnnotation.batchModal.answer')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='text-text-secondary'>
|
||||
<tr>
|
||||
<td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.question')} 1</td>
|
||||
<td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.answer')} 1</td>
|
||||
<td className='h-9 pl-3 pr-2 border-b border-divider-subtle text-[13px]'>{t('appAnnotation.batchModal.question')} 1</td>
|
||||
<td className='h-9 pl-3 pr-2 border-b border-divider-subtle text-[13px]'>{t('appAnnotation.batchModal.answer')} 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='h-9 pl-3 pr-2 text-[13px]'>{t('appAnnotation.batchModal.question')} 2</td>
|
||||
@ -55,14 +55,14 @@ const CSVDownload: FC = () => {
|
||||
</table>
|
||||
</div>
|
||||
<CSVDownloader
|
||||
className="mt-2 block cursor-pointer"
|
||||
className="block mt-2 cursor-pointer"
|
||||
type={Type.Link}
|
||||
filename={`template-${locale}`}
|
||||
bom={true}
|
||||
data={getTemplate()}
|
||||
>
|
||||
<div className='system-xs-medium flex h-[18px] items-center space-x-1 text-text-accent'>
|
||||
<DownloadIcon className='mr-1 h-3 w-3' />
|
||||
<div className='flex items-center h-[18px] space-x-1 text-text-accent system-xs-medium'>
|
||||
<DownloadIcon className='w-3 h-3 mr-1' />
|
||||
{t('appAnnotation.batchModal.template')}
|
||||
</div>
|
||||
</CSVDownloader>
|
||||
|
||||
@ -91,29 +91,29 @@ const CSVUploader: FC<Props> = ({
|
||||
/>
|
||||
<div ref={dropRef}>
|
||||
{!file && (
|
||||
<div className={cn('system-sm-regular flex h-20 items-center rounded-xl border border-dashed border-components-dropzone-border bg-components-dropzone-bg', dragging && 'border border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}>
|
||||
<div className='flex w-full items-center justify-center space-x-2'>
|
||||
<div className={cn('flex items-center h-20 rounded-xl bg-components-dropzone-bg border border-dashed border-components-dropzone-border system-sm-regular', dragging && 'bg-components-dropzone-bg-accent border border-components-dropzone-border-accent')}>
|
||||
<div className='w-full flex items-center justify-center space-x-2'>
|
||||
<CSVIcon className="shrink-0" />
|
||||
<div className='text-text-tertiary'>
|
||||
{t('appAnnotation.batchModal.csvUploadTitle')}
|
||||
<span className='cursor-pointer text-text-accent' onClick={selectHandle}>{t('appAnnotation.batchModal.browse')}</span>
|
||||
<span className='text-text-accent cursor-pointer' onClick={selectHandle}>{t('appAnnotation.batchModal.browse')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />}
|
||||
{dragging && <div ref={dragRef} className='absolute w-full h-full top-0 left-0' />}
|
||||
</div>
|
||||
)}
|
||||
{file && (
|
||||
<div className={cn('group flex h-20 items-center rounded-xl border border-components-panel-border bg-components-panel-bg px-6 text-sm font-normal', 'hover:border-components-panel-bg-blur hover:bg-components-panel-bg-blur')}>
|
||||
<div className={cn('flex items-center h-20 px-6 rounded-xl bg-components-panel-bg border border-components-panel-border text-sm font-normal group', 'hover:bg-components-panel-bg-blur hover:border-components-panel-bg-blur')}>
|
||||
<CSVIcon className="shrink-0" />
|
||||
<div className='ml-2 flex w-0 grow'>
|
||||
<span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-text-primary'>{file.name.replace(/.csv$/, '')}</span>
|
||||
<div className='flex ml-2 w-0 grow'>
|
||||
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-text-primary'>{file.name.replace(/.csv$/, '')}</span>
|
||||
<span className='shrink-0 text-text-tertiary'>.csv</span>
|
||||
</div>
|
||||
<div className='hidden items-center group-hover:flex'>
|
||||
<div className='hidden group-hover:flex items-center'>
|
||||
<Button variant='secondary' onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
|
||||
<div className='mx-2 h-4 w-px bg-divider-regular' />
|
||||
<div className='cursor-pointer p-2' onClick={removeFile}>
|
||||
<RiDeleteBinLine className='h-4 w-4 text-text-tertiary' />
|
||||
<div className='mx-2 w-px h-4 bg-divider-regular' />
|
||||
<div className='p-2 cursor-pointer' onClick={removeFile}>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -87,10 +87,10 @@ const BatchModal: FC<IBatchModalProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={() => { }} className='!max-w-[520px] !rounded-xl px-8 py-6'>
|
||||
<div className='system-xl-medium relative pb-1 text-text-primary'>{t('appAnnotation.batchModal.title')}</div>
|
||||
<div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onCancel}>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
<Modal isShow={isShow} onClose={() => { }} className='px-8 py-6 !max-w-[520px] !rounded-xl'>
|
||||
<div className='relative pb-1 system-xl-medium text-text-primary'>{t('appAnnotation.batchModal.title')}</div>
|
||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onCancel}>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<CSVUploader
|
||||
file={currentCSV}
|
||||
@ -104,8 +104,8 @@ const BatchModal: FC<IBatchModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mt-[28px] flex justify-end pt-6'>
|
||||
<Button className='system-sm-medium mr-2 text-text-tertiary' onClick={onCancel}>
|
||||
<div className='mt-[28px] pt-6 flex justify-end'>
|
||||
<Button className='mr-2 text-text-tertiary system-sm-medium' onClick={onCancel}>
|
||||
{t('appAnnotation.batchModal.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@ -20,11 +20,11 @@ type Props = {
|
||||
}
|
||||
|
||||
export const EditTitle: FC<{ className?: string; title: string }> = ({ className, title }) => (
|
||||
<div className={cn(className, 'system-xs-medium flex h-[18px] items-center text-text-tertiary')}>
|
||||
<RiEditFill className='mr-1 h-3.5 w-3.5' />
|
||||
<div className={cn(className, 'flex items-center h-[18px] system-xs-medium text-text-tertiary')}>
|
||||
<RiEditFill className='mr-1 w-3.5 h-3.5' />
|
||||
<div>{title}</div>
|
||||
<div
|
||||
className='ml-2 h-[1px] grow'
|
||||
className='ml-2 grow h-[1px]'
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, rgba(0, 0, 0, 0.05) -1.65%, rgba(0, 0, 0, 0.00) 100%)',
|
||||
}}
|
||||
@ -40,7 +40,7 @@ const EditItem: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const [newContent, setNewContent] = useState('')
|
||||
const showNewContent = newContent && newContent !== content
|
||||
const avatar = type === EditItemType.Query ? <User className='h-6 w-6' /> : <Robot className='h-6 w-6' />
|
||||
const avatar = type === EditItemType.Query ? <User className='w-6 h-6' /> : <Robot className='w-6 h-6' />
|
||||
const name = type === EditItemType.Query ? t('appAnnotation.editModal.queryName') : t('appAnnotation.editModal.answerName')
|
||||
const editTitle = type === EditItemType.Query ? t('appAnnotation.editModal.yourQuery') : t('appAnnotation.editModal.yourAnswer')
|
||||
const placeholder = type === EditItemType.Query ? t('appAnnotation.editModal.queryPlaceholder') : t('appAnnotation.editModal.answerPlaceholder')
|
||||
@ -58,11 +58,11 @@ const EditItem: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className='flex' onClick={e => e.stopPropagation()}>
|
||||
<div className='mr-3 shrink-0'>
|
||||
<div className='shrink-0 mr-3'>
|
||||
{avatar}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='system-xs-semibold mb-1 text-text-primary'>{name}</div>
|
||||
<div className='mb-1 system-xs-semibold text-text-primary'>{name}</div>
|
||||
<div className='system-sm-regular text-text-primary'>{content}</div>
|
||||
{!isEdit
|
||||
? (
|
||||
@ -70,34 +70,34 @@ const EditItem: FC<Props> = ({
|
||||
{showNewContent && (
|
||||
<div className='mt-3'>
|
||||
<EditTitle title={editTitle} />
|
||||
<div className='system-sm-regular mt-1 text-text-primary'>{newContent}</div>
|
||||
<div className='mt-1 system-sm-regular text-text-primary'>{newContent}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-2 flex items-center'>
|
||||
{!readonly && (
|
||||
<div
|
||||
className='system-xs-medium flex cursor-pointer items-center space-x-1 text-text-accent'
|
||||
className='flex items-center space-x-1 system-xs-medium text-text-accent cursor-pointer'
|
||||
onClick={() => {
|
||||
setIsEdit(true)
|
||||
}}
|
||||
>
|
||||
<RiEditLine className='mr-1 h-3.5 w-3.5' />
|
||||
<RiEditLine className='mr-1 w-3.5 h-3.5' />
|
||||
<div>{t('common.operation.edit')}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showNewContent && (
|
||||
<div className='system-xs-medium ml-2 flex items-center text-text-tertiary'>
|
||||
<div className='ml-2 flex items-center system-xs-medium text-text-tertiary'>
|
||||
<div className='mr-2'>·</div>
|
||||
<div
|
||||
className='flex cursor-pointer items-center space-x-1'
|
||||
className='flex items-center space-x-1 cursor-pointer'
|
||||
onClick={() => {
|
||||
setNewContent(content)
|
||||
onSave(content)
|
||||
}}
|
||||
>
|
||||
<div className='h-3.5 w-3.5'>
|
||||
<RiDeleteBinLine className='h-3.5 w-3.5' />
|
||||
<div className='w-3.5 h-3.5'>
|
||||
<RiDeleteBinLine className='w-3.5 h-3.5' />
|
||||
</div>
|
||||
<div>{t('common.operation.delete')}</div>
|
||||
</div>
|
||||
|
||||
@ -86,7 +86,7 @@ const EditAnnotationModal: FC<Props> = ({
|
||||
title={t('appAnnotation.editModal.title') as string}
|
||||
body={(
|
||||
<div>
|
||||
<div className='space-y-6 p-6 pb-4'>
|
||||
<div className='p-6 pb-4 space-y-6'>
|
||||
<EditItem
|
||||
type={EditItemType.Query}
|
||||
content={query}
|
||||
@ -115,7 +115,7 @@ const EditAnnotationModal: FC<Props> = ({
|
||||
foot={
|
||||
<div>
|
||||
{isAnnotationFull && (
|
||||
<div className='mb-4 mt-6 px-6'>
|
||||
<div className='mt-6 mb-4 px-6'>
|
||||
<AnnotationFull />
|
||||
</div>
|
||||
)}
|
||||
@ -123,9 +123,9 @@ const EditAnnotationModal: FC<Props> = ({
|
||||
{
|
||||
annotationId
|
||||
? (
|
||||
<div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'>
|
||||
<div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center space-x-2 pl-3'
|
||||
className='flex items-center pl-3 space-x-2 cursor-pointer'
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<MessageCheckRemove />
|
||||
|
||||
@ -13,10 +13,10 @@ const EmptyElement: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex h-full items-center justify-center'>
|
||||
<div className='box-border h-fit w-[560px] rounded-2xl bg-background-section-burn px-5 py-4'>
|
||||
<span className='system-md-semibold text-text-secondary'>{t('appAnnotation.noData.title')}<ThreeDotsIcon className='relative -left-1.5 -top-3 inline' /></span>
|
||||
<div className='system-sm-regular mt-2 text-text-tertiary'>
|
||||
<div className='flex items-center justify-center h-full'>
|
||||
<div className='bg-background-section-burn w-[560px] h-fit box-border px-5 py-4 rounded-2xl'>
|
||||
<span className='text-text-secondary system-md-semibold'>{t('appAnnotation.noData.title')}<ThreeDotsIcon className='inline relative -top-3 -left-1.5' /></span>
|
||||
<div className='mt-2 text-text-tertiary system-sm-regular'>
|
||||
{t('appAnnotation.noData.description')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,15 +6,15 @@ import useSWR from 'swr'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { fetchAnnotationsCount } from '@/service/log'
|
||||
|
||||
export type QueryParam = {
|
||||
export interface QueryParam {
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
type IFilterProps = {
|
||||
interface IFilterProps {
|
||||
appId: string
|
||||
queryParams: QueryParam
|
||||
setQueryParams: (v: QueryParam) => void
|
||||
children: React.JSX.Element
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
const Filter: FC<IFilterProps> = ({
|
||||
@ -29,7 +29,7 @@ const Filter: FC<IFilterProps> = ({
|
||||
if (!data)
|
||||
return null
|
||||
return (
|
||||
<div className='mb-2 flex flex-row flex-wrap items-center justify-between gap-2'>
|
||||
<div className='flex justify-between flex-row flex-wrap gap-2 items-center mb-2'>
|
||||
<Input
|
||||
wrapperClassName='w-[200px]'
|
||||
showLeftIcon
|
||||
|
||||
@ -10,7 +10,7 @@ import { useContext } from 'use-context-selector'
|
||||
import {
|
||||
useCSVDownloader,
|
||||
} from 'react-papaparse'
|
||||
import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import Button from '../../../base/button'
|
||||
import AddAnnotationModal from '../add-annotation-modal'
|
||||
import type { AnnotationItemBasic } from '../type'
|
||||
@ -80,18 +80,18 @@ const HeaderOptions: FC<Props> = ({
|
||||
const Operations = () => {
|
||||
return (
|
||||
<div className="w-full py-1">
|
||||
<button className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50' onClick={() => {
|
||||
<button className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]' onClick={() => {
|
||||
setShowBulkImportModal(true)
|
||||
}}>
|
||||
<FilePlus02 className='h-4 w-4 text-text-tertiary' />
|
||||
<span className='system-sm-regular grow text-left text-text-secondary'>{t('appAnnotation.table.header.bulkImport')}</span>
|
||||
<FilePlus02 className='w-4 h-4 text-text-tertiary' />
|
||||
<span className='grow text-text-secondary system-sm-regular text-left'>{t('appAnnotation.table.header.bulkImport')}</span>
|
||||
</button>
|
||||
<Menu as="div" className="relative h-full w-full">
|
||||
<MenuButton className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50'>
|
||||
<FileDownload02 className='h-4 w-4 text-text-tertiary' />
|
||||
<span className='system-sm-regular grow text-left text-text-secondary'>{t('appAnnotation.table.header.bulkExport')}</span>
|
||||
<ChevronRight className='h-[14px] w-[14px] shrink-0 text-text-tertiary' />
|
||||
</MenuButton>
|
||||
<Menu as="div" className="relative w-full h-full">
|
||||
<Menu.Button className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]'>
|
||||
<FileDownload02 className='w-4 h-4 text-text-tertiary' />
|
||||
<span className='grow text-text-secondary system-sm-regular text-left'>{t('appAnnotation.table.header.bulkExport')}</span>
|
||||
<ChevronRight className='shrink-0 w-[14px] h-[14px] text-text-tertiary' />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
@ -101,9 +101,9 @@ const HeaderOptions: FC<Props> = ({
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
<Menu.Items
|
||||
className={cn(
|
||||
'absolute left-1 top-[1px] z-10 min-w-[100px] origin-top-right -translate-x-full rounded-xl border-[0.5px] border-components-panel-on-panel-item-bg bg-components-panel-bg py-1 shadow-xs',
|
||||
'absolute top-[1px] left-1 -translate-x-full py-1 min-w-[100px] z-10 bg-components-panel-bg border-[0.5px] border-components-panel-on-panel-item-bg origin-top-right rounded-xl shadow-xs',
|
||||
)}
|
||||
>
|
||||
<CSVDownloader
|
||||
@ -115,14 +115,14 @@ const HeaderOptions: FC<Props> = ({
|
||||
...list.map(item => [item.question, item.answer]),
|
||||
]}
|
||||
>
|
||||
<button disabled={annotationUnavailable} className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50'>
|
||||
<span className='system-sm-regular grow text-left text-text-secondary'>CSV</span>
|
||||
<button disabled={annotationUnavailable} className='h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]'>
|
||||
<span className='grow text-text-secondary system-sm-regular text-left'>CSV</span>
|
||||
</button>
|
||||
</CSVDownloader>
|
||||
<button disabled={annotationUnavailable} className={cn('mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 hover:bg-components-panel-on-panel-item-bg-hover disabled:opacity-50', '!border-0')} onClick={JSONLOutput}>
|
||||
<span className='system-sm-regular grow text-left text-text-secondary'>JSONL</span>
|
||||
<button disabled={annotationUnavailable} className={cn('h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-components-panel-on-panel-item-bg-hover rounded-lg cursor-pointer disabled:opacity-50 w-[calc(100%_-_8px)]', '!border-0')} onClick={JSONLOutput}>
|
||||
<span className='grow text-text-secondary system-sm-regular text-left'>JSONL</span>
|
||||
</button>
|
||||
</MenuItems>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
@ -134,7 +134,7 @@ const HeaderOptions: FC<Props> = ({
|
||||
return (
|
||||
<div className='flex space-x-2'>
|
||||
<Button variant='primary' onClick={() => setShowAddModal(true)}>
|
||||
<RiAddLine className='mr-0.5 h-4 w-4' />
|
||||
<RiAddLine className='w-4 h-4 mr-0.5' />
|
||||
<div>{t('appAnnotation.table.header.addAnnotation')}</div>
|
||||
</Button>
|
||||
<CustomPopover
|
||||
@ -143,11 +143,11 @@ const HeaderOptions: FC<Props> = ({
|
||||
trigger="click"
|
||||
btnElement={
|
||||
<Button variant='secondary' className='w-8 p-0'>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
<RiMoreFill className='w-4 h-4' />
|
||||
</Button>
|
||||
}
|
||||
btnClassName='p-0 border-0'
|
||||
className={'!z-20 h-fit !w-[155px]'}
|
||||
className={'!w-[155px] h-fit !z-20'}
|
||||
popupClassName='!w-full !overflow-visible'
|
||||
manualClose
|
||||
/>
|
||||
|
||||
@ -152,15 +152,15 @@ const Annotation: FC<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-col'>
|
||||
<p className='system-sm-regular text-text-tertiary'>{t('appLog.description')}</p>
|
||||
<div className='flex flex-1 flex-col py-4'>
|
||||
<div className='flex flex-col h-full'>
|
||||
<p className='text-text-tertiary system-sm-regular'>{t('appLog.description')}</p>
|
||||
<div className='flex flex-col py-4 flex-1'>
|
||||
<Filter appId={appDetail.id} queryParams={queryParams} setQueryParams={setQueryParams}>
|
||||
<div className='flex items-center space-x-2'>
|
||||
{isChatApp && (
|
||||
<>
|
||||
<div className={cn(!annotationConfig?.enabled && 'pr-2', 'flex h-7 items-center space-x-1 rounded-lg border border-components-panel-border bg-components-panel-bg-blur pl-2')}>
|
||||
<MessageFast className='h-4 w-4 text-util-colors-indigo-indigo-600' />
|
||||
<div className={cn(!annotationConfig?.enabled && 'pr-2', 'flex items-center h-7 rounded-lg bg-components-panel-bg-blur border border-components-panel-border pl-2 space-x-1')}>
|
||||
<MessageFast className='w-4 h-4 text-util-colors-indigo-indigo-600' />
|
||||
<div className='system-sm-medium text-text-primary'>{t('appAnnotation.name')}</div>
|
||||
<Switch
|
||||
key={controlRefreshSwitch}
|
||||
@ -188,14 +188,14 @@ const Annotation: FC<Props> = ({
|
||||
></Switch>
|
||||
{annotationConfig?.enabled && (
|
||||
<div className='flex items-center pl-1.5'>
|
||||
<div className='mr-1 h-3.5 w-[1px] shrink-0 bg-divider-subtle'></div>
|
||||
<div className='shrink-0 mr-1 w-[1px] h-3.5 bg-divider-subtle'></div>
|
||||
<ActionButton onClick={() => setIsShowEdit(true)}>
|
||||
<RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
|
||||
<RiEqualizer2Line className='w-4 h-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mx-3 h-3.5 w-[1px] shrink-0 bg-divider-regular'></div>
|
||||
<div className='shrink-0 mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -217,7 +217,7 @@ const Annotation: FC<Props> = ({
|
||||
onRemove={handleRemove}
|
||||
onView={handleView}
|
||||
/>
|
||||
: <div className='flex h-full grow items-center justify-center'><EmptyElement /></div>
|
||||
: <div className='grow flex h-full items-center justify-center'><EmptyElement /></div>
|
||||
}
|
||||
{/* Show Pagination only if the total is more than the limit */}
|
||||
{(total && total > APP_PAGE_LIMIT)
|
||||
|
||||
@ -9,7 +9,7 @@ import ActionButton from '@/app/components/base/action-button'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
list: AnnotationItem[]
|
||||
onRemove: (id: string) => void
|
||||
onView: (item: AnnotationItem) => void
|
||||
@ -29,18 +29,18 @@ const List: FC<Props> = ({
|
||||
<table className={cn('mt-2 w-full min-w-[440px] border-collapse border-0')}>
|
||||
<thead className='system-xs-medium-uppercase text-text-tertiary'>
|
||||
<tr>
|
||||
<td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'>{t('appAnnotation.table.header.question')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.answer')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.createdAt')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.hits')}</td>
|
||||
<td className='w-[96px] whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.table.header.actions')}</td>
|
||||
<td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.question')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.answer')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.createdAt')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.table.header.hits')}</td>
|
||||
<td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap w-[96px]'>{t('appAnnotation.table.header.actions')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="system-sm-regular text-text-secondary">
|
||||
<tbody className="text-text-secondary system-sm-regular">
|
||||
{list.map(item => (
|
||||
<tr
|
||||
key={item.id}
|
||||
className='cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover'
|
||||
className='border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer'
|
||||
onClick={
|
||||
() => {
|
||||
onView(item)
|
||||
@ -48,11 +48,11 @@ const List: FC<Props> = ({
|
||||
}
|
||||
>
|
||||
<td
|
||||
className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2'
|
||||
className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||
title={item.question}
|
||||
>{item.question}</td>
|
||||
<td
|
||||
className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2'
|
||||
className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||
title={item.answer}
|
||||
>{item.answer}</td>
|
||||
<td className='p-3 pr-2'>{formatTime(item.created_at, t('appLog.dateTimeFormat') as string)}</td>
|
||||
@ -61,7 +61,7 @@ const List: FC<Props> = ({
|
||||
{/* Actions */}
|
||||
<div className='flex space-x-1 text-text-tertiary'>
|
||||
<ActionButton onClick={() => onView(item)}>
|
||||
<RiEditLine className='h-4 w-4' />
|
||||
<RiEditLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick={() => {
|
||||
@ -69,7 +69,7 @@ const List: FC<Props> = ({
|
||||
setShowConfirmDelete(true)
|
||||
}}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -7,9 +7,9 @@ import { ClockFastForward } from '@/app/components/base/icons/src/vender/line/ti
|
||||
const HitHistoryNoData: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='mx-auto mt-20 w-[480px] space-y-2 rounded-2xl bg-background-section-burn p-5'>
|
||||
<div className='inline-block rounded-lg border border-divider-subtle p-3'>
|
||||
<ClockFastForward className='h-5 w-5 text-text-tertiary' />
|
||||
<div className='mx-auto mt-20 w-[480px] p-5 rounded-2xl bg-background-section-burn space-y-2'>
|
||||
<div className='inline-block p-3 rounded-lg border border-divider-subtle'>
|
||||
<ClockFastForward className='w-5 h-5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='system-sm-regular text-text-tertiary'>{t('appAnnotation.viewModal.noHitHistory')}</div>
|
||||
</div>
|
||||
|
||||
@ -116,30 +116,30 @@ const ViewAnnotationModal: FC<Props> = ({
|
||||
<table className={cn('w-full min-w-[440px] border-collapse border-0')} >
|
||||
<thead className="system-xs-medium-uppercase text-text-tertiary">
|
||||
<tr>
|
||||
<td className='w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1'>{t('appAnnotation.hitHistoryTable.query')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.match')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.response')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.source')}</td>
|
||||
<td className='whitespace-nowrap bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.score')}</td>
|
||||
<td className='w-[160px] whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3'>{t('appAnnotation.hitHistoryTable.time')}</td>
|
||||
<td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.query')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.match')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.response')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.score')}</td>
|
||||
<td className='pl-3 py-1.5 rounded-r-lg bg-background-section-burn whitespace-nowrap w-[160px]'>{t('appAnnotation.hitHistoryTable.time')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="system-sm-regular text-text-secondary">
|
||||
<tbody className="text-text-secondary system-sm-regular">
|
||||
{hitHistoryList.map(item => (
|
||||
<tr
|
||||
key={item.id}
|
||||
className={'cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover'}
|
||||
className={'border-b border-divider-subtle hover:bg-background-default-hover cursor-pointer'}
|
||||
>
|
||||
<td
|
||||
className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2'
|
||||
className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||
title={item.question}
|
||||
>{item.question}</td>
|
||||
<td
|
||||
className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2'
|
||||
className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||
title={item.match}
|
||||
>{item.match}</td>
|
||||
<td
|
||||
className='max-w-[250px] overflow-hidden text-ellipsis whitespace-nowrap p-3 pr-2'
|
||||
className='p-3 pr-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
|
||||
title={item.response}
|
||||
>{item.response}</td>
|
||||
<td className='p-3 pr-2'>{item.source}</td>
|
||||
@ -168,7 +168,7 @@ const ViewAnnotationModal: FC<Props> = ({
|
||||
maxWidthClassName='!max-w-[800px]'
|
||||
title={
|
||||
<TabSlider
|
||||
className='relative top-[9px] shrink-0'
|
||||
className='shrink-0 relative top-[9px]'
|
||||
value={activeTab}
|
||||
onChange={v => setActiveTab(v as TabType)}
|
||||
options={tabs}
|
||||
@ -178,7 +178,7 @@ const ViewAnnotationModal: FC<Props> = ({
|
||||
}
|
||||
body={(
|
||||
<div>
|
||||
<div className='space-y-6 p-6 pb-4'>
|
||||
<div className='p-6 pb-4 space-y-6'>
|
||||
{activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
|
||||
</div>
|
||||
<Confirm
|
||||
@ -195,9 +195,9 @@ const ViewAnnotationModal: FC<Props> = ({
|
||||
)}
|
||||
foot={id
|
||||
? (
|
||||
<div className='system-sm-medium flex h-16 items-center justify-between rounded-bl-xl rounded-br-xl border-t border-divider-subtle bg-background-section-burn px-4 text-text-tertiary'>
|
||||
<div className='px-4 flex h-16 items-center justify-between border-t border-divider-subtle bg-background-section-burn rounded-bl-xl rounded-br-xl system-sm-medium text-text-tertiary'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center space-x-2 pl-3'
|
||||
className='flex items-center pl-3 space-x-2 cursor-pointer'
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<MessageCheckRemove />
|
||||
|
||||
@ -136,8 +136,8 @@ const AppPublisher = ({
|
||||
if (publishDisabled || published)
|
||||
return
|
||||
handlePublish()
|
||||
},
|
||||
{ exactMatch: true, useCapture: true })
|
||||
}
|
||||
, { exactMatch: true, useCapture: true })
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -157,19 +157,19 @@ const AppPublisher = ({
|
||||
disabled={disabled}
|
||||
>
|
||||
{t('workflow.common.publish')}
|
||||
<RiArrowDownSLine className='h-4 w-4 text-components-button-primary-text' />
|
||||
<RiArrowDownSLine className='w-4 h-4 text-components-button-primary-text' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<div className='w-[320px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'>
|
||||
<div className='w-[320px] bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl shadow-shadow-shadow-5'>
|
||||
<div className='p-4 pt-3'>
|
||||
<div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'>
|
||||
<div className='flex items-center h-6 system-xs-medium-uppercase text-text-tertiary'>
|
||||
{publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')}
|
||||
</div>
|
||||
{publishedAt
|
||||
? (
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='system-sm-medium flex items-center text-text-secondary'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center system-sm-medium text-text-secondary'>
|
||||
{t('workflow.common.publishedAt')} {formatTimeFromNow(publishedAt)}
|
||||
</div>
|
||||
{isChatApp && <Button
|
||||
@ -183,7 +183,7 @@ const AppPublisher = ({
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='system-sm-medium flex items-center text-text-secondary'>
|
||||
<div className='flex items-center system-sm-medium text-text-secondary'>
|
||||
{t('workflow.common.autoSaved')} · {Boolean(draftUpdatedAt) && formatTimeFromNow(draftUpdatedAt!)}
|
||||
</div>
|
||||
)}
|
||||
@ -198,7 +198,7 @@ const AppPublisher = ({
|
||||
: (
|
||||
<Button
|
||||
variant='primary'
|
||||
className='mt-3 w-full'
|
||||
className='w-full mt-3'
|
||||
onClick={() => handlePublish()}
|
||||
disabled={publishDisabled || published}
|
||||
>
|
||||
@ -210,7 +210,7 @@ const AppPublisher = ({
|
||||
<span>{t('workflow.common.publishUpdate')}</span>
|
||||
<div className='flex gap-0.5'>
|
||||
{PUBLISH_SHORTCUT.map(key => (
|
||||
<span key={key} className='system-kbd h-4 w-4 rounded-[4px] bg-components-kbd-bg-white text-text-primary-on-surface'>
|
||||
<span key={key} className='w-4 h-4 text-text-primary-on-surface system-kbd rounded-[4px] bg-components-kbd-bg-white'>
|
||||
{key}
|
||||
</span>
|
||||
))}
|
||||
@ -222,11 +222,11 @@ const AppPublisher = ({
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-t-divider-regular p-4 pt-3'>
|
||||
<div className='p-4 pt-3 border-t-[0.5px] border-t-divider-regular'>
|
||||
<SuggestedAction
|
||||
disabled={!publishedAt}
|
||||
link={appURL}
|
||||
icon={<RiPlayCircleLine className='h-4 w-4' />}
|
||||
icon={<RiPlayCircleLine className='w-4 h-4' />}
|
||||
>
|
||||
{t('workflow.common.runApp')}
|
||||
</SuggestedAction>
|
||||
@ -235,7 +235,7 @@ const AppPublisher = ({
|
||||
<SuggestedAction
|
||||
disabled={!publishedAt}
|
||||
link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`}
|
||||
icon={<RiPlayList2Line className='h-4 w-4' />}
|
||||
icon={<RiPlayList2Line className='w-4 h-4' />}
|
||||
>
|
||||
{t('workflow.common.batchRunApp')}
|
||||
</SuggestedAction>
|
||||
@ -247,7 +247,7 @@ const AppPublisher = ({
|
||||
handleTrigger()
|
||||
}}
|
||||
disabled={!publishedAt}
|
||||
icon={<CodeBrowser className='h-4 w-4' />}
|
||||
icon={<CodeBrowser className='w-4 h-4' />}
|
||||
>
|
||||
{t('workflow.common.embedIntoSite')}
|
||||
</SuggestedAction>
|
||||
@ -257,14 +257,14 @@ const AppPublisher = ({
|
||||
publishedAt && handleOpenInExplore()
|
||||
}}
|
||||
disabled={!publishedAt}
|
||||
icon={<RiPlanetLine className='h-4 w-4' />}
|
||||
icon={<RiPlanetLine className='w-4 h-4' />}
|
||||
>
|
||||
{t('workflow.common.openInExplore')}
|
||||
</SuggestedAction>
|
||||
<SuggestedAction
|
||||
disabled={!publishedAt}
|
||||
link='./develop'
|
||||
icon={<RiTerminalBoxLine className='h-4 w-4' />}
|
||||
icon={<RiTerminalBoxLine className='w-4 h-4' />}
|
||||
>
|
||||
{t('workflow.common.accessAPIReference')}
|
||||
</SuggestedAction>
|
||||
|
||||
@ -73,25 +73,25 @@ const PublishWithMultipleModel: FC<PublishWithMultipleModelProps> = ({
|
||||
className='mt-3 w-full'
|
||||
>
|
||||
{t('appDebug.operation.applyConfig')}
|
||||
<RiArrowDownSLine className='ml-0.5 h-3 w-3' />
|
||||
<RiArrowDownSLine className='ml-0.5 w-3 h-3' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50 mt-1 w-[288px]'>
|
||||
<div className='rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg'>
|
||||
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>
|
||||
<PortalToFollowElemContent className='mt-1 w-[288px] z-50'>
|
||||
<div className='p-1 rounded-lg border-[0.5px] border-components-panel-border shadow-lg bg-components-panel-bg'>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>
|
||||
{t('appDebug.publishAs')}
|
||||
</div>
|
||||
{
|
||||
validModelConfigs.map((item, index) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-tertiary hover:bg-state-base-hover'
|
||||
className='flex items-center h-8 px-3 text-sm text-text-tertiary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
<span className='min-w-[18px] italic'>#{index + 1}</span>
|
||||
<span className='italic min-w-[18px]'>#{index + 1}</span>
|
||||
<ModelIcon modelName={item.model} provider={item.providerItem} className='ml-2' />
|
||||
<div
|
||||
className='ml-1 truncate text-text-secondary'
|
||||
className='ml-1 text-text-secondary truncate'
|
||||
title={item.modelItem.label[language]}
|
||||
>
|
||||
{item.modelItem.label[language]}
|
||||
|
||||
@ -20,9 +20,9 @@ const SuggestedAction = ({ icon, link, disabled, children, className, ...props }
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className='relative h-4 w-4'>{icon}</div>
|
||||
<div className='system-sm-medium shrink grow basis-0'>{children}</div>
|
||||
<RiArrowRightUpLine className='h-3.5 w-3.5' />
|
||||
<div className='relative w-4 h-4'>{icon}</div>
|
||||
<div className='grow shrink basis-0 system-sm-medium'>{children}</div>
|
||||
<RiArrowRightUpLine className='w-3.5 h-3.5' />
|
||||
</a>
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user