Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0652f3d0aa | ||
|
|
803947c1ae | ||
|
|
58c62f0a34 | ||
|
|
2174225259 | ||
|
|
6a0f1dad7f | ||
|
|
40cadab8a6 | ||
|
|
6157f57872 | ||
|
|
c4bb07184d | ||
|
|
af5d6ca27d | ||
|
|
78f2ec8f32 | ||
|
|
a113356695 | ||
|
|
43753c8e9a | ||
|
|
fc8c765215 | ||
|
|
86a1859d02 | ||
|
|
360986f38d | ||
|
|
1be0d26c1f | ||
|
|
c167a1f4f4 | ||
|
|
5eb0ca9b9d | ||
|
|
6e26ed2bb7 | ||
|
|
058d9c3525 | ||
|
|
b247fbb2ef | ||
|
|
bc6f122364 | ||
|
|
815d77856d | ||
|
|
05eaef84bb | ||
|
|
770c461a8f | ||
|
|
16b6ffd915 | ||
|
|
9701b573e0 | ||
|
|
83cd14104d | ||
|
|
e2988acc2f | ||
|
|
cea4669b76 | ||
|
|
17b4d4c7b2 | ||
|
|
e95f0fcceb | ||
|
|
6d5d6f0f24 | ||
|
|
7ce8faf176 | ||
|
|
f31e3313b0 | ||
|
|
f6ac98a37d | ||
|
|
f8e7e301cd | ||
|
|
35bafb3235 | ||
|
|
ae5d2ecf48 | ||
|
|
1907d2a90a | ||
|
|
4448a54cc1 | ||
|
|
bfc0d606dc | ||
|
|
3306228840 | ||
|
|
d7e00ae691 | ||
|
|
0e2e2db3fa |
@@ -6,8 +6,8 @@ on:
|
||||
- "main"
|
||||
- "deploy/dev"
|
||||
- "deploy/enterprise"
|
||||
release:
|
||||
types: [published]
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
concurrency:
|
||||
group: build-push-${{ github.head_ref || github.run_id }}
|
||||
|
||||
+4
-3
@@ -26,9 +26,6 @@ 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
|
||||
@@ -50,6 +47,9 @@ 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
|
||||
@@ -297,6 +297,7 @@ OCEANBASE_VECTOR_USER=root@test
|
||||
OCEANBASE_VECTOR_PASSWORD=difyai123456
|
||||
OCEANBASE_VECTOR_DATABASE=test
|
||||
OCEANBASE_MEMORY_LIMIT=6G
|
||||
OCEANBASE_ENABLE_HYBRID_SEARCH=false
|
||||
|
||||
# openGauss configuration
|
||||
OPENGAUSS_HOST=127.0.0.1
|
||||
|
||||
@@ -26,6 +26,7 @@ from models.dataset import Document as DatasetDocument
|
||||
from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation
|
||||
from models.provider import Provider, ProviderModel
|
||||
from services.account_service import RegisterService, TenantService
|
||||
from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs
|
||||
from services.plugin.data_migration import PluginDataMigration
|
||||
from services.plugin.plugin_migration import PluginMigration
|
||||
|
||||
@@ -792,3 +793,23 @@ def install_plugins(input_file: str, output_file: str, workers: int):
|
||||
PluginMigration.install_plugins(input_file, output_file, workers)
|
||||
|
||||
click.echo(click.style("Install plugins completed.", fg="green"))
|
||||
|
||||
|
||||
@click.command("clear-free-plan-tenant-expired-logs", help="Clear free plan tenant expired logs.")
|
||||
@click.option("--days", prompt=True, help="The days to clear free plan tenant expired logs.", default=30)
|
||||
@click.option("--batch", prompt=True, help="The batch size to clear free plan tenant expired logs.", default=100)
|
||||
@click.option(
|
||||
"--tenant_ids",
|
||||
prompt=True,
|
||||
multiple=True,
|
||||
help="The tenant ids to clear free plan tenant expired logs.",
|
||||
)
|
||||
def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[str]):
|
||||
"""
|
||||
Clear free plan tenant expired logs.
|
||||
"""
|
||||
click.echo(click.style("Starting clear free plan tenant expired logs.", fg="white"))
|
||||
|
||||
ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids)
|
||||
|
||||
click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green"))
|
||||
|
||||
@@ -33,3 +33,9 @@ class OceanBaseVectorConfig(BaseSettings):
|
||||
description="Name of the OceanBase Vector database to connect to",
|
||||
default=None,
|
||||
)
|
||||
|
||||
OCEANBASE_ENABLE_HYBRID_SEARCH: bool = Field(
|
||||
description="Enable hybrid search features (requires OceanBase >= 4.3.5.1). Set to false for compatibility "
|
||||
"with older versions",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@@ -43,3 +43,8 @@ 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.2",
|
||||
default="1.1.3",
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
||||
@@ -50,7 +50,15 @@ class AppListApi(Resource):
|
||||
parser.add_argument(
|
||||
"mode",
|
||||
type=str,
|
||||
choices=["chat", "workflow", "agent-chat", "channel", "all"],
|
||||
choices=[
|
||||
"completion",
|
||||
"chat",
|
||||
"advanced-chat",
|
||||
"workflow",
|
||||
"agent-chat",
|
||||
"channel",
|
||||
"all",
|
||||
],
|
||||
default="all",
|
||||
location="args",
|
||||
required=False,
|
||||
@@ -130,7 +138,6 @@ 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()
|
||||
|
||||
|
||||
@@ -646,7 +646,6 @@ class DatasetRetrievalSettingApi(Resource):
|
||||
| VectorType.BAIDU
|
||||
| VectorType.VIKINGDB
|
||||
| VectorType.UPSTASH
|
||||
| VectorType.OCEANBASE
|
||||
):
|
||||
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
||||
case (
|
||||
@@ -664,6 +663,7 @@ class DatasetRetrievalSettingApi(Resource):
|
||||
| VectorType.COUCHBASE
|
||||
| VectorType.MILVUS
|
||||
| VectorType.OPENGAUSS
|
||||
| VectorType.OCEANBASE
|
||||
):
|
||||
return {
|
||||
"retrieval_method": [
|
||||
@@ -692,7 +692,6 @@ class DatasetRetrievalSettingMockApi(Resource):
|
||||
| VectorType.BAIDU
|
||||
| VectorType.VIKINGDB
|
||||
| VectorType.UPSTASH
|
||||
| VectorType.OCEANBASE
|
||||
):
|
||||
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
||||
case (
|
||||
@@ -708,6 +707,7 @@ class DatasetRetrievalSettingMockApi(Resource):
|
||||
| VectorType.PGVECTOR
|
||||
| VectorType.LINDORM
|
||||
| VectorType.OPENGAUSS
|
||||
| VectorType.OCEANBASE
|
||||
):
|
||||
return {
|
||||
"retrieval_method": [
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
|
||||
@@ -19,7 +20,7 @@ class EnterpriseWorkspace(Resource):
|
||||
parser.add_argument("owner_email", type=str, required=True, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
account = Account.query.filter_by(email=args["owner_email"]).first()
|
||||
account = db.session.query(Account).filter_by(email=args["owner_email"]).first()
|
||||
if account is None:
|
||||
return {"message": "owner account not found."}, 404
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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
|
||||
@@ -13,10 +14,20 @@ 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 segment_fields
|
||||
from models.dataset import Dataset, DocumentSegment
|
||||
from fields.segment_fields import child_chunk_fields, segment_fields
|
||||
from models.dataset import Dataset
|
||||
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):
|
||||
@@ -70,10 +81,12 @@ class SegmentApi(DatasetApiResource):
|
||||
return {"error": "Segments is required"}, 400
|
||||
|
||||
def get(self, tenant_id, dataset_id, document_id):
|
||||
"""Create single segment."""
|
||||
"""Get segments."""
|
||||
# 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.")
|
||||
@@ -107,19 +120,23 @@ class SegmentApi(DatasetApiResource):
|
||||
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
|
||||
segments, total = SegmentService.get_segments(
|
||||
document_id=document_id,
|
||||
tenant_id=current_user.current_tenant_id,
|
||||
status_list=args["status"],
|
||||
keyword=args["keyword"],
|
||||
)
|
||||
|
||||
if status_list:
|
||||
query = query.filter(DocumentSegment.status.in_(status_list))
|
||||
response = {
|
||||
"data": marshal(segments, segment_fields),
|
||||
"doc_form": document.doc_form,
|
||||
"total": total,
|
||||
"has_more": len(segments) == limit,
|
||||
"limit": limit,
|
||||
"page": page,
|
||||
}
|
||||
|
||||
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
|
||||
return response, 200
|
||||
|
||||
|
||||
class DatasetSegmentApi(DatasetApiResource):
|
||||
@@ -138,9 +155,8 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
if not document:
|
||||
raise NotFound("Document not found.")
|
||||
# check segment
|
||||
segment = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||
).first()
|
||||
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.")
|
||||
SegmentService.delete_segment(segment, document, dataset)
|
||||
@@ -179,9 +195,7 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
# check segment
|
||||
segment_id = str(segment_id)
|
||||
segment = DocumentSegment.query.filter(
|
||||
DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id
|
||||
).first()
|
||||
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.")
|
||||
|
||||
@@ -190,12 +204,200 @@ class DatasetSegmentApi(DatasetApiResource):
|
||||
parser.add_argument("segment", type=dict, required=False, nullable=True, location="json")
|
||||
args = parser.parse_args()
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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,6 +27,9 @@ 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
|
||||
@@ -37,6 +40,8 @@ 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):
|
||||
@@ -59,18 +64,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(
|
||||
"Too many requests. Please try again later. The current maximum "
|
||||
"concurrent requests allowed is {}.".format(self.max_active_requests)
|
||||
f"Too many requests. Please try again later. The current maximum concurrent requests allowed "
|
||||
f"for {self.client_id} is {self.max_active_requests}."
|
||||
)
|
||||
redis_client.hset(self.active_requests_key, request_id, str(time.time()))
|
||||
return request_id
|
||||
@@ -80,6 +85,9 @@ 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,6 +49,7 @@ class FileAttribute(StrEnum):
|
||||
TRANSFER_METHOD = "transfer_method"
|
||||
URL = "url"
|
||||
EXTENSION = "extension"
|
||||
RELATED_ID = "related_id"
|
||||
|
||||
|
||||
class ArrayFileAttribute(StrEnum):
|
||||
|
||||
@@ -34,6 +34,8 @@ 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(
|
||||
|
||||
@@ -187,7 +187,7 @@ class IndexingRunner:
|
||||
},
|
||||
)
|
||||
if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX:
|
||||
child_chunks = document_segment.child_chunks
|
||||
child_chunks = document_segment.get_child_chunks()
|
||||
if child_chunks:
|
||||
child_documents = []
|
||||
for child_chunk in child_chunks:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Written by YORKI MINAKO🤡, Edited by Xiaoyi
|
||||
CONVERSATION_TITLE_PROMPT = """You need to decompose the user's input into "subject" and "intention" in order to accurately figure out what the user's input language actually is.
|
||||
Notice: the language type user use could be diverse, which can be English, Chinese, Español, Arabic, Japanese, French, and etc.
|
||||
Notice: the language type user use could be diverse, which can be English, Chinese, Italian, Español, Arabic, Japanese, French, and etc.
|
||||
MAKE SURE your output is the SAME language as the user's input!
|
||||
Your output is restricted only to: (Input language) Intention + Subject(short as possible)
|
||||
Your output MUST be a valid JSON.
|
||||
|
||||
@@ -33,7 +33,6 @@ 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
|
||||
@@ -41,6 +40,13 @@ 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,
|
||||
@@ -58,7 +64,7 @@ provider_config_map: dict[str, dict[str, Any]] = {
|
||||
"config_class": OpikConfig,
|
||||
"secret_keys": ["api_key"],
|
||||
"other_keys": ["project", "url", "workspace"],
|
||||
"trace_instance": OpikDataTrace,
|
||||
"trace_instance": lambda config: build_opik_trace_instance(config),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ 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)
|
||||
@@ -222,6 +223,7 @@ class RetrievalService:
|
||||
all_documents: list,
|
||||
retrieval_method: str,
|
||||
exceptions: list,
|
||||
document_ids_filter: Optional[list[str]] = None,
|
||||
):
|
||||
with flask_app.app_context():
|
||||
try:
|
||||
@@ -231,7 +233,9 @@ class RetrievalService:
|
||||
|
||||
vector_processor = Vector(dataset=dataset)
|
||||
|
||||
documents = vector_processor.search_by_full_text(cls.escape_query_for_search(query), top_k=top_k)
|
||||
documents = vector_processor.search_by_full_text(
|
||||
cls.escape_query_for_search(query), top_k=top_k, document_ids_filter=document_ids_filter
|
||||
)
|
||||
if documents:
|
||||
if (
|
||||
reranking_model
|
||||
|
||||
@@ -102,8 +102,6 @@ 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] = {
|
||||
@@ -167,7 +165,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", 10)
|
||||
top_k = kwargs.get("top_k", 3)
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
filters = []
|
||||
if document_ids_filter:
|
||||
@@ -210,7 +208,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", 10)
|
||||
top_k = kwargs.get("top_k", 3)
|
||||
filters = kwargs.get("filter", [])
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
if document_ids_filter:
|
||||
@@ -295,7 +293,7 @@ class LindormVectorStore(BaseVector):
|
||||
|
||||
|
||||
def default_text_mapping(dimension: int, method_name: str, **kwargs: Any) -> dict:
|
||||
excludes_from_source = kwargs.get("excludes_from_source")
|
||||
excludes_from_source = kwargs.get("excludes_from_source", False)
|
||||
analyzer = kwargs.get("analyzer", "ik_max_word")
|
||||
text_field = kwargs.get("text_field", Field.CONTENT_KEY.value)
|
||||
engine = kwargs["engine"]
|
||||
@@ -356,12 +354,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": excludes_from_source}
|
||||
mapping["mappings"]["_source"] = {"excludes": [vector_field]}
|
||||
|
||||
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
|
||||
|
||||
@@ -458,7 +456,7 @@ def default_vector_search_query(
|
||||
"query": {"knn": {vector_field: {"vector": query_vector, "k": k}}},
|
||||
}
|
||||
|
||||
if filters is not None:
|
||||
if filters is not None and len(filters) > 0:
|
||||
# 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
|
||||
|
||||
@@ -231,8 +231,8 @@ class MilvusVector(BaseVector):
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
filter = ""
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
filter = f'metadata["document_id"] in ({document_ids})'
|
||||
document_ids = ", ".join(f'"{id}"' for id in document_ids_filter)
|
||||
filter = f'metadata["document_id"] in [{document_ids}]'
|
||||
results = self._client.search(
|
||||
collection_name=self._collection_name,
|
||||
data=[query_vector],
|
||||
@@ -259,7 +259,7 @@ class MilvusVector(BaseVector):
|
||||
filter = ""
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
filter = f'metadata["document_id"] in ({document_ids})'
|
||||
filter = f'metadata["document_id"] in [{document_ids}]'
|
||||
|
||||
results = self._client.search(
|
||||
collection_name=self._collection_name,
|
||||
|
||||
@@ -31,6 +31,7 @@ class OceanBaseVectorConfig(BaseModel):
|
||||
user: str
|
||||
password: str
|
||||
database: str
|
||||
enable_hybrid_search: bool = False
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -57,6 +58,7 @@ class OceanBaseVector(BaseVector):
|
||||
password=self._config.password,
|
||||
db_name=self._config.database,
|
||||
)
|
||||
self._hybrid_search_enabled = self._check_hybrid_search_support() # Check if hybrid search is supported
|
||||
|
||||
def get_type(self) -> str:
|
||||
return VectorType.OCEANBASE
|
||||
@@ -98,6 +100,16 @@ class OceanBaseVector(BaseVector):
|
||||
columns=cols,
|
||||
vidxs=vidx_params,
|
||||
)
|
||||
try:
|
||||
if self._hybrid_search_enabled:
|
||||
self._client.perform_raw_text_sql(f"""ALTER TABLE {self._collection_name}
|
||||
ADD FULLTEXT INDEX fulltext_index_for_col_text (text) WITH PARSER ik""")
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
"Failed to add fulltext index to the target table, your OceanBase version must be 4.3.5.1 or above "
|
||||
+ "to support fulltext index and vector index in the same table",
|
||||
e,
|
||||
)
|
||||
vals = []
|
||||
params = self._client.perform_raw_text_sql("SHOW PARAMETERS LIKE '%ob_vector_memory_limit_percentage%'")
|
||||
for row in params:
|
||||
@@ -116,6 +128,27 @@ class OceanBaseVector(BaseVector):
|
||||
)
|
||||
redis_client.set(collection_exist_cache_key, 1, ex=3600)
|
||||
|
||||
def _check_hybrid_search_support(self) -> bool:
|
||||
"""
|
||||
Check if the current OceanBase version supports hybrid search.
|
||||
Returns True if the version is >= 4.3.5.1, otherwise False.
|
||||
"""
|
||||
if not self._config.enable_hybrid_search:
|
||||
return False
|
||||
|
||||
try:
|
||||
from packaging import version
|
||||
|
||||
# return OceanBase_CE 4.3.5.1 (r101000042025031818-bxxxx) (Built Mar 18 2025 18:13:36)
|
||||
result = self._client.perform_raw_text_sql("SELECT @@version_comment AS version")
|
||||
ob_full_version = result.fetchone()[0]
|
||||
ob_version = ob_full_version.split()[1]
|
||||
logger.debug("Current OceanBase version is %s", ob_version)
|
||||
return version.parse(ob_version).base_version >= version.parse("4.3.5.1").base_version
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to check OceanBase version: {str(e)}. Disabling hybrid search.")
|
||||
return False
|
||||
|
||||
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
ids = self._get_uuids(documents)
|
||||
for id, doc, emb in zip(ids, documents, embeddings):
|
||||
@@ -130,7 +163,7 @@ class OceanBaseVector(BaseVector):
|
||||
)
|
||||
|
||||
def text_exists(self, id: str) -> bool:
|
||||
cur = self._client.get(table_name=self._collection_name, id=id)
|
||||
cur = self._client.get(table_name=self._collection_name, ids=id)
|
||||
return bool(cur.rowcount != 0)
|
||||
|
||||
def delete_by_ids(self, ids: list[str]) -> None:
|
||||
@@ -139,9 +172,12 @@ class OceanBaseVector(BaseVector):
|
||||
self._client.delete(table_name=self._collection_name, ids=ids)
|
||||
|
||||
def get_ids_by_metadata_field(self, key: str, value: str) -> list[str]:
|
||||
from sqlalchemy import text
|
||||
|
||||
cur = self._client.get(
|
||||
table_name=self._collection_name,
|
||||
where_clause=f"metadata->>'$.{key}' = '{value}'",
|
||||
ids=None,
|
||||
where_clause=[text(f"metadata->>'$.{key}' = '{value}'")],
|
||||
output_column_name=["id"],
|
||||
)
|
||||
return [row[0] for row in cur]
|
||||
@@ -151,36 +187,84 @@ class OceanBaseVector(BaseVector):
|
||||
self.delete_by_ids(ids)
|
||||
|
||||
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
|
||||
return []
|
||||
if not self._hybrid_search_enabled:
|
||||
return []
|
||||
|
||||
try:
|
||||
top_k = kwargs.get("top_k", 5)
|
||||
if not isinstance(top_k, int) or top_k <= 0:
|
||||
raise ValueError("top_k must be a positive integer")
|
||||
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
where_clause = ""
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
where_clause = f" AND metadata->>'$.document_id' IN ({document_ids})"
|
||||
|
||||
full_sql = f"""SELECT metadata, text, MATCH (text) AGAINST (:query) AS score
|
||||
FROM {self._collection_name}
|
||||
WHERE MATCH (text) AGAINST (:query) > 0
|
||||
{where_clause}
|
||||
ORDER BY score DESC
|
||||
LIMIT {top_k}"""
|
||||
|
||||
with self._client.engine.connect() as conn:
|
||||
with conn.begin():
|
||||
from sqlalchemy import text
|
||||
|
||||
result = conn.execute(text(full_sql), {"query": query})
|
||||
rows = result.fetchall()
|
||||
|
||||
docs = []
|
||||
for row in rows:
|
||||
metadata_str, _text, score = row
|
||||
try:
|
||||
metadata = json.loads(metadata_str)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Invalid JSON metadata: {metadata_str}")
|
||||
metadata = {}
|
||||
metadata["score"] = score
|
||||
docs.append(Document(page_content=_text, metadata=metadata))
|
||||
|
||||
return docs
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fulltext search: {str(e)}.")
|
||||
return []
|
||||
|
||||
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
|
||||
document_ids_filter = kwargs.get("document_ids_filter")
|
||||
where_clause = None
|
||||
_where_clause = None
|
||||
if document_ids_filter:
|
||||
document_ids = ", ".join(f"'{id}'" for id in document_ids_filter)
|
||||
where_clause = f"metadata->>'$.document_id' in ({document_ids})"
|
||||
from sqlalchemy import text
|
||||
|
||||
_where_clause = [text(where_clause)]
|
||||
ef_search = kwargs.get("ef_search", self._hnsw_ef_search)
|
||||
if ef_search != self._hnsw_ef_search:
|
||||
self._client.set_ob_hnsw_ef_search(ef_search)
|
||||
self._hnsw_ef_search = ef_search
|
||||
topk = kwargs.get("top_k", 10)
|
||||
cur = self._client.ann_search(
|
||||
table_name=self._collection_name,
|
||||
vec_column_name="vector",
|
||||
vec_data=query_vector,
|
||||
topk=topk,
|
||||
distance_func=func.l2_distance,
|
||||
output_column_names=["text", "metadata"],
|
||||
with_dist=True,
|
||||
where_clause=where_clause,
|
||||
)
|
||||
try:
|
||||
cur = self._client.ann_search(
|
||||
table_name=self._collection_name,
|
||||
vec_column_name="vector",
|
||||
vec_data=query_vector,
|
||||
topk=topk,
|
||||
distance_func=func.l2_distance,
|
||||
output_column_names=["text", "metadata"],
|
||||
with_dist=True,
|
||||
where_clause=_where_clause,
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception("Failed to search by vector. ", e)
|
||||
docs = []
|
||||
for text, metadata, distance in cur:
|
||||
for _text, metadata, distance in cur:
|
||||
metadata = json.loads(metadata)
|
||||
metadata["score"] = 1 - distance / math.sqrt(2)
|
||||
docs.append(
|
||||
Document(
|
||||
page_content=text,
|
||||
page_content=_text,
|
||||
metadata=metadata,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ class OpenGaussConfig(BaseModel):
|
||||
database: str
|
||||
min_connection: int
|
||||
max_connection: int
|
||||
enable_pq: bool = False # Enable PQ acceleration
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -57,6 +58,11 @@ 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);
|
||||
@@ -68,6 +74,7 @@ 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
|
||||
@@ -97,7 +104,26 @@ class OpenGauss(BaseVector):
|
||||
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
dimension = len(embeddings[0])
|
||||
self._create_collection(dimension)
|
||||
return self.add_texts(texts, embeddings)
|
||||
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)
|
||||
|
||||
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
|
||||
values = []
|
||||
@@ -211,8 +237,6 @@ 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)
|
||||
|
||||
|
||||
@@ -236,5 +260,6 @@ 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 metadata->>'document_id' in ({document_ids}) "
|
||||
where_clause = f" WHERE meta->>'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 metadata->>'document_id' in ({document_ids}) "
|
||||
where_clause = f" AND meta->>'document_id' in ({document_ids}) "
|
||||
if self.pg_bigm:
|
||||
cur.execute("SET pg_bigm.similarity_limit TO 0.000001")
|
||||
cur.execute(
|
||||
|
||||
@@ -610,7 +610,11 @@ 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
|
||||
retrieval_method="keyword_search",
|
||||
dataset_id=dataset.id,
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
document_ids_filter=document_ids_filter,
|
||||
)
|
||||
if documents:
|
||||
all_documents.extend(documents)
|
||||
@@ -896,7 +900,10 @@ class DatasetRetrieval:
|
||||
return str(inputs.get(key, f"{{{{{key}}}}}"))
|
||||
|
||||
pattern = re.compile(r"\{\{(\w+)\}\}")
|
||||
return pattern.sub(replacer, text)
|
||||
output = pattern.sub(replacer, text)
|
||||
if isinstance(output, str):
|
||||
output = re.sub(r"[\r\n\t]+", " ", output).strip()
|
||||
return output
|
||||
|
||||
def _automatic_metadata_filter_func(
|
||||
self, dataset_ids: list, query: str, tenant_id: str, user_id: str, metadata_model_config: ModelConfig
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping, Sequence
|
||||
@@ -360,8 +361,13 @@ class KnowledgeRetrievalNode(LLMNode):
|
||||
if isinstance(expected_value, str):
|
||||
expected_value = self.graph_runtime_state.variable_pool.convert_template(
|
||||
expected_value
|
||||
).text
|
||||
|
||||
).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")
|
||||
filters = self._process_metadata_filter_func(
|
||||
condition.comparison_operator, metadata_name, expected_value, filters
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ from dify_app import DifyApp
|
||||
def init_app(app: DifyApp):
|
||||
from commands import (
|
||||
add_qdrant_index,
|
||||
clear_free_plan_tenant_expired_logs,
|
||||
convert_to_agent_apps,
|
||||
create_tenant,
|
||||
extract_plugins,
|
||||
@@ -34,6 +35,7 @@ def init_app(app: DifyApp):
|
||||
extract_unique_plugins,
|
||||
install_plugins,
|
||||
old_metadata_migration,
|
||||
clear_free_plan_tenant_expired_logs,
|
||||
]
|
||||
for cmd in cmds_to_register:
|
||||
app.cli.add_command(cmd)
|
||||
|
||||
@@ -24,6 +24,7 @@ vector_setting_fields = {
|
||||
}
|
||||
|
||||
weighted_score_fields = {
|
||||
"weight_type": fields.String,
|
||||
"keyword_setting": fields.Nested(keyword_setting_fields),
|
||||
"vector_setting": fields.Nested(vector_setting_fields),
|
||||
}
|
||||
|
||||
@@ -720,6 +720,23 @@ class DocumentSegment(db.Model): # type: ignore[name-defined]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_child_chunks(self):
|
||||
process_rule = self.document.dataset_process_rule
|
||||
if process_rule.mode == "hierarchical":
|
||||
rules = Rule(**process_rule.rules_dict)
|
||||
if rules.parent_mode:
|
||||
child_chunks = (
|
||||
db.session.query(ChildChunk)
|
||||
.filter(ChildChunk.segment_id == self.id)
|
||||
.order_by(ChildChunk.position.asc())
|
||||
.all()
|
||||
)
|
||||
return child_chunks or []
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
return []
|
||||
|
||||
@property
|
||||
def sign_content(self):
|
||||
return self.get_sign_content()
|
||||
|
||||
@@ -838,6 +838,33 @@ class Conversation(db.Model): # type: ignore[name-defined]
|
||||
def in_debug_mode(self):
|
||||
return self.override_model_configs is not None
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"app_id": self.app_id,
|
||||
"app_model_config_id": self.app_model_config_id,
|
||||
"model_provider": self.model_provider,
|
||||
"override_model_configs": self.override_model_configs,
|
||||
"model_id": self.model_id,
|
||||
"mode": self.mode,
|
||||
"name": self.name,
|
||||
"summary": self.summary,
|
||||
"inputs": self.inputs,
|
||||
"introduction": self.introduction,
|
||||
"system_instruction": self.system_instruction,
|
||||
"system_instruction_tokens": self.system_instruction_tokens,
|
||||
"status": self.status,
|
||||
"invoke_from": self.invoke_from,
|
||||
"from_source": self.from_source,
|
||||
"from_end_user_id": self.from_end_user_id,
|
||||
"from_account_id": self.from_account_id,
|
||||
"read_at": self.read_at,
|
||||
"read_account_id": self.read_account_id,
|
||||
"dialogue_count": self.dialogue_count,
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
}
|
||||
|
||||
|
||||
class Message(db.Model): # type: ignore[name-defined]
|
||||
__tablename__ = "messages"
|
||||
|
||||
@@ -15,11 +15,11 @@ from services.feature_service import FeatureService
|
||||
|
||||
|
||||
@app.celery.task(queue="dataset")
|
||||
def send_document_clean_notify_task():
|
||||
def mail_clean_document_notify_task():
|
||||
"""
|
||||
Async Send document clean notify mail
|
||||
|
||||
Usage: send_document_clean_notify_task.delay()
|
||||
Usage: mail_clean_document_notify_task.delay()
|
||||
"""
|
||||
if not mail.is_inited():
|
||||
return
|
||||
|
||||
@@ -9,7 +9,6 @@ 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
|
||||
@@ -37,9 +36,13 @@ class AppService:
|
||||
filters = [App.tenant_id == tenant_id, App.is_universal == False]
|
||||
|
||||
if args["mode"] == "workflow":
|
||||
filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))
|
||||
filters.append(App.mode == AppMode.WORKFLOW.value)
|
||||
elif args["mode"] == "completion":
|
||||
filters.append(App.mode == AppMode.COMPLETION.value)
|
||||
elif args["mode"] == "chat":
|
||||
filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value]))
|
||||
filters.append(App.mode == AppMode.CHAT.value)
|
||||
elif args["mode"] == "advanced-chat":
|
||||
filters.append(App.mode == AppMode.ADVANCED_CHAT.value)
|
||||
elif args["mode"] == "agent-chat":
|
||||
filters.append(App.mode == AppMode.AGENT_CHAT.value)
|
||||
elif args["mode"] == "channel":
|
||||
@@ -222,7 +225,6 @@ 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")
|
||||
@@ -231,9 +233,6 @@ 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:
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import click
|
||||
from flask import Flask, current_app
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_storage import storage
|
||||
from models.account import Tenant
|
||||
from models.model import App, Conversation, Message
|
||||
from models.workflow import WorkflowNodeExecution, WorkflowRun
|
||||
from services.billing_service import BillingService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClearFreePlanTenantExpiredLogs:
|
||||
@classmethod
|
||||
def process_tenant(cls, flask_app: Flask, tenant_id: str, days: int, batch: int):
|
||||
with flask_app.app_context():
|
||||
apps = db.session.query(App).filter(App.tenant_id == tenant_id).all()
|
||||
app_ids = [app.id for app in apps]
|
||||
while True:
|
||||
with Session(db.engine).no_autoflush as session:
|
||||
messages = (
|
||||
session.query(Message)
|
||||
.filter(
|
||||
Message.app_id.in_(app_ids),
|
||||
Message.created_at < datetime.datetime.now() - datetime.timedelta(days=days),
|
||||
)
|
||||
.limit(batch)
|
||||
.all()
|
||||
)
|
||||
if len(messages) == 0:
|
||||
break
|
||||
|
||||
storage.save(
|
||||
f"free_plan_tenant_expired_logs/"
|
||||
f"{tenant_id}/messages/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||
f"-{time.time()}.json",
|
||||
json.dumps(
|
||||
jsonable_encoder(
|
||||
[message.to_dict() for message in messages],
|
||||
),
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
message_ids = [message.id for message in messages]
|
||||
|
||||
# delete messages
|
||||
session.query(Message).filter(
|
||||
Message.id.in_(message_ids),
|
||||
).delete(synchronize_session=False)
|
||||
|
||||
session.commit()
|
||||
|
||||
click.echo(
|
||||
click.style(
|
||||
f"[{datetime.datetime.now()}] Processed {len(message_ids)} messages for tenant {tenant_id} "
|
||||
)
|
||||
)
|
||||
|
||||
while True:
|
||||
with Session(db.engine).no_autoflush as session:
|
||||
conversations = (
|
||||
session.query(Conversation)
|
||||
.filter(
|
||||
Conversation.app_id.in_(app_ids),
|
||||
Conversation.updated_at < datetime.datetime.now() - datetime.timedelta(days=days),
|
||||
)
|
||||
.limit(batch)
|
||||
.all()
|
||||
)
|
||||
|
||||
if len(conversations) == 0:
|
||||
break
|
||||
|
||||
storage.save(
|
||||
f"free_plan_tenant_expired_logs/"
|
||||
f"{tenant_id}/conversations/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||
f"-{time.time()}.json",
|
||||
json.dumps(
|
||||
jsonable_encoder(
|
||||
[conversation.to_dict() for conversation in conversations],
|
||||
),
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
conversation_ids = [conversation.id for conversation in conversations]
|
||||
session.query(Conversation).filter(
|
||||
Conversation.id.in_(conversation_ids),
|
||||
).delete(synchronize_session=False)
|
||||
session.commit()
|
||||
|
||||
click.echo(
|
||||
click.style(
|
||||
f"[{datetime.datetime.now()}] Processed {len(conversation_ids)}"
|
||||
f" conversations for tenant {tenant_id}"
|
||||
)
|
||||
)
|
||||
|
||||
while True:
|
||||
with Session(db.engine).no_autoflush as session:
|
||||
workflow_node_executions = (
|
||||
session.query(WorkflowNodeExecution)
|
||||
.filter(
|
||||
WorkflowNodeExecution.tenant_id == tenant_id,
|
||||
WorkflowNodeExecution.created_at < datetime.datetime.now() - datetime.timedelta(days=days),
|
||||
)
|
||||
.limit(batch)
|
||||
.all()
|
||||
)
|
||||
|
||||
if len(workflow_node_executions) == 0:
|
||||
break
|
||||
|
||||
# save workflow node executions
|
||||
storage.save(
|
||||
f"free_plan_tenant_expired_logs/"
|
||||
f"{tenant_id}/workflow_node_executions/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||
f"-{time.time()}.json",
|
||||
json.dumps(
|
||||
jsonable_encoder(workflow_node_executions),
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
workflow_node_execution_ids = [
|
||||
workflow_node_execution.id for workflow_node_execution in workflow_node_executions
|
||||
]
|
||||
|
||||
# delete workflow node executions
|
||||
session.query(WorkflowNodeExecution).filter(
|
||||
WorkflowNodeExecution.id.in_(workflow_node_execution_ids),
|
||||
).delete(synchronize_session=False)
|
||||
session.commit()
|
||||
|
||||
click.echo(
|
||||
click.style(
|
||||
f"[{datetime.datetime.now()}] Processed {len(workflow_node_execution_ids)}"
|
||||
f" workflow node executions for tenant {tenant_id}"
|
||||
)
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
|
||||
if len(workflow_runs) == 0:
|
||||
break
|
||||
|
||||
# save workflow runs
|
||||
|
||||
storage.save(
|
||||
f"free_plan_tenant_expired_logs/"
|
||||
f"{tenant_id}/workflow_runs/{datetime.datetime.now().strftime('%Y-%m-%d')}"
|
||||
f"-{time.time()}.json",
|
||||
json.dumps(
|
||||
jsonable_encoder(
|
||||
[workflow_run.to_dict() for workflow_run in workflow_runs],
|
||||
),
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
workflow_run_ids = [workflow_run.id for workflow_run in workflow_runs]
|
||||
|
||||
# delete workflow runs
|
||||
session.query(WorkflowRun).filter(
|
||||
WorkflowRun.id.in_(workflow_run_ids),
|
||||
).delete(synchronize_session=False)
|
||||
session.commit()
|
||||
|
||||
@classmethod
|
||||
def process(cls, days: int, batch: int, tenant_ids: list[str]):
|
||||
"""
|
||||
Clear free plan tenant expired logs.
|
||||
"""
|
||||
|
||||
click.echo(click.style("Clearing free plan tenant expired logs", fg="white"))
|
||||
ended_at = datetime.datetime.now()
|
||||
started_at = datetime.datetime(2023, 4, 3, 8, 59, 24)
|
||||
current_time = started_at
|
||||
|
||||
with Session(db.engine) as session:
|
||||
total_tenant_count = session.query(Tenant.id).count()
|
||||
|
||||
click.echo(click.style(f"Total tenant count: {total_tenant_count}", fg="white"))
|
||||
|
||||
handled_tenant_count = 0
|
||||
|
||||
thread_pool = ThreadPoolExecutor(max_workers=10)
|
||||
|
||||
def process_tenant(flask_app: Flask, tenant_id: str) -> None:
|
||||
try:
|
||||
if (
|
||||
not dify_config.BILLING_ENABLED
|
||||
or BillingService.get_info(tenant_id)["subscription"]["plan"] == "sandbox"
|
||||
):
|
||||
# only process sandbox tenant
|
||||
cls.process_tenant(flask_app, tenant_id, days, batch)
|
||||
except Exception:
|
||||
logger.exception(f"Failed to process tenant {tenant_id}")
|
||||
finally:
|
||||
nonlocal handled_tenant_count
|
||||
handled_tenant_count += 1
|
||||
if handled_tenant_count % 100 == 0:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"[{datetime.datetime.now()}] "
|
||||
f"Processed {handled_tenant_count} tenants "
|
||||
f"({(handled_tenant_count / total_tenant_count) * 100:.1f}%), "
|
||||
f"{handled_tenant_count}/{total_tenant_count}",
|
||||
fg="green",
|
||||
)
|
||||
)
|
||||
|
||||
futures = []
|
||||
|
||||
if tenant_ids:
|
||||
for tenant_id in tenant_ids:
|
||||
futures.append(
|
||||
thread_pool.submit(
|
||||
process_tenant,
|
||||
current_app._get_current_object(), # type: ignore[attr-defined]
|
||||
tenant_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
while current_time < ended_at:
|
||||
click.echo(
|
||||
click.style(f"Current time: {current_time}, Started at: {datetime.datetime.now()}", fg="white")
|
||||
)
|
||||
# Initial interval of 1 day, will be dynamically adjusted based on tenant count
|
||||
interval = datetime.timedelta(days=1)
|
||||
# Process tenants in this batch
|
||||
with Session(db.engine) as session:
|
||||
# Calculate tenant count in next batch with current interval
|
||||
# Try different intervals until we find one with a reasonable tenant count
|
||||
test_intervals = [
|
||||
datetime.timedelta(days=1),
|
||||
datetime.timedelta(hours=12),
|
||||
datetime.timedelta(hours=6),
|
||||
datetime.timedelta(hours=3),
|
||||
datetime.timedelta(hours=1),
|
||||
]
|
||||
|
||||
for test_interval in test_intervals:
|
||||
tenant_count = (
|
||||
session.query(Tenant.id)
|
||||
.filter(Tenant.created_at.between(current_time, current_time + test_interval))
|
||||
.count()
|
||||
)
|
||||
if tenant_count <= 100:
|
||||
interval = test_interval
|
||||
break
|
||||
else:
|
||||
# If all intervals have too many tenants, use minimum interval
|
||||
interval = datetime.timedelta(hours=1)
|
||||
|
||||
# Adjust interval to target ~100 tenants per batch
|
||||
if tenant_count > 0:
|
||||
# Scale interval based on ratio to target count
|
||||
interval = min(
|
||||
datetime.timedelta(days=1), # Max 1 day
|
||||
max(
|
||||
datetime.timedelta(hours=1), # Min 1 hour
|
||||
interval * (100 / tenant_count), # Scale to target 100
|
||||
),
|
||||
)
|
||||
|
||||
batch_end = min(current_time + interval, ended_at)
|
||||
|
||||
rs = (
|
||||
session.query(Tenant.id)
|
||||
.filter(Tenant.created_at.between(current_time, batch_end))
|
||||
.order_by(Tenant.created_at)
|
||||
)
|
||||
|
||||
tenants = []
|
||||
for row in rs:
|
||||
tenant_id = str(row.id)
|
||||
try:
|
||||
tenants.append(tenant_id)
|
||||
except Exception:
|
||||
logger.exception(f"Failed to process tenant {tenant_id}")
|
||||
continue
|
||||
|
||||
futures.append(
|
||||
thread_pool.submit(
|
||||
process_tenant,
|
||||
current_app._get_current_object(), # type: ignore[attr-defined]
|
||||
tenant_id,
|
||||
)
|
||||
)
|
||||
|
||||
current_time = batch_end
|
||||
|
||||
# wait for all threads to finish
|
||||
for future in futures:
|
||||
future.result()
|
||||
@@ -2140,6 +2140,88 @@ 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
|
||||
|
||||
@@ -95,7 +95,7 @@ class WeightKeywordSetting(BaseModel):
|
||||
|
||||
|
||||
class WeightModel(BaseModel):
|
||||
weight_type: str
|
||||
weight_type: Optional[str] = None
|
||||
vector_setting: Optional[WeightVectorSetting] = None
|
||||
keyword_setting: Optional[WeightKeywordSetting] = None
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def add_document_to_index_task(dataset_document_id: str):
|
||||
},
|
||||
)
|
||||
if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX:
|
||||
child_chunks = segment.child_chunks
|
||||
child_chunks = segment.get_child_chunks()
|
||||
if child_chunks:
|
||||
child_documents = []
|
||||
for child_chunk in child_chunks:
|
||||
|
||||
@@ -130,7 +130,7 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str):
|
||||
},
|
||||
)
|
||||
if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX:
|
||||
child_chunks = segment.child_chunks
|
||||
child_chunks = segment.get_child_chunks()
|
||||
if child_chunks:
|
||||
child_documents = []
|
||||
for child_chunk in child_chunks:
|
||||
|
||||
@@ -63,7 +63,7 @@ def enable_segment_to_index_task(segment_id: str):
|
||||
|
||||
index_processor = IndexProcessorFactory(dataset_document.doc_form).init_index_processor()
|
||||
if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX:
|
||||
child_chunks = segment.child_chunks
|
||||
child_chunks = segment.get_child_chunks()
|
||||
if child_chunks:
|
||||
child_documents = []
|
||||
for child_chunk in child_chunks:
|
||||
|
||||
@@ -67,7 +67,7 @@ def enable_segments_to_index_task(segment_ids: list, dataset_id: str, document_i
|
||||
)
|
||||
|
||||
if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX:
|
||||
child_chunks = segment.child_chunks
|
||||
child_chunks = segment.get_child_chunks()
|
||||
if child_chunks:
|
||||
child_documents = []
|
||||
for child_chunk in child_chunks:
|
||||
|
||||
@@ -554,6 +554,7 @@ OCEANBASE_VECTOR_PASSWORD=difyai123456
|
||||
OCEANBASE_VECTOR_DATABASE=test
|
||||
OCEANBASE_CLUSTER_NAME=difyai
|
||||
OCEANBASE_MEMORY_LIMIT=6G
|
||||
OCEANBASE_ENABLE_HYBRID_SEARCH=false
|
||||
|
||||
# opengauss configurations, only available when VECTOR_STORE is `opengauss`
|
||||
OPENGAUSS_HOST=opengauss
|
||||
@@ -563,6 +564,7 @@ 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.2
|
||||
image: langgenius/dify-api:1.1.3
|
||||
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.2
|
||||
image: langgenius/dify-api:1.1.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -53,7 +53,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.1.2
|
||||
image: langgenius/dify-web:1.1.3
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
@@ -373,7 +373,8 @@ services:
|
||||
|
||||
# OceanBase vector database
|
||||
oceanbase:
|
||||
image: quay.io/oceanbase/oceanbase-ce:4.3.3.0-100000142024101215
|
||||
image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818
|
||||
container_name: oceanbase
|
||||
profiles:
|
||||
- oceanbase
|
||||
restart: always
|
||||
@@ -386,7 +387,9 @@ services:
|
||||
OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
|
||||
OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
|
||||
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai}
|
||||
OB_SERVER_IP: '127.0.0.1'
|
||||
MODE: MINI
|
||||
ports:
|
||||
- "${OCEANBASE_VECTOR_PORT:-2881}:2881"
|
||||
|
||||
# Oracle vector database
|
||||
oracle:
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
|
||||
@@ -252,6 +252,7 @@ x-shared-env: &shared-api-worker-env
|
||||
OCEANBASE_VECTOR_DATABASE: ${OCEANBASE_VECTOR_DATABASE:-test}
|
||||
OCEANBASE_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai}
|
||||
OCEANBASE_MEMORY_LIMIT: ${OCEANBASE_MEMORY_LIMIT:-6G}
|
||||
OCEANBASE_ENABLE_HYBRID_SEARCH: ${OCEANBASE_ENABLE_HYBRID_SEARCH:-false}
|
||||
OPENGAUSS_HOST: ${OPENGAUSS_HOST:-opengauss}
|
||||
OPENGAUSS_PORT: ${OPENGAUSS_PORT:-6600}
|
||||
OPENGAUSS_USER: ${OPENGAUSS_USER:-postgres}
|
||||
@@ -259,6 +260,7 @@ 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}
|
||||
@@ -432,7 +434,7 @@ x-shared-env: &shared-api-worker-env
|
||||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.1.2
|
||||
image: langgenius/dify-api:1.1.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -459,7 +461,7 @@ services:
|
||||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.1.2
|
||||
image: langgenius/dify-api:1.1.3
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
@@ -483,7 +485,7 @@ services:
|
||||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.1.2
|
||||
image: langgenius/dify-web:1.1.3
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
@@ -540,7 +542,7 @@ services:
|
||||
|
||||
# The DifySandbox
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.10
|
||||
image: langgenius/dify-sandbox:0.2.11
|
||||
restart: always
|
||||
environment:
|
||||
# The DifySandbox configurations
|
||||
@@ -803,7 +805,8 @@ services:
|
||||
|
||||
# OceanBase vector database
|
||||
oceanbase:
|
||||
image: quay.io/oceanbase/oceanbase-ce:4.3.3.0-100000142024101215
|
||||
image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818
|
||||
container_name: oceanbase
|
||||
profiles:
|
||||
- oceanbase
|
||||
restart: always
|
||||
@@ -816,7 +819,9 @@ services:
|
||||
OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
|
||||
OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456}
|
||||
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai}
|
||||
OB_SERVER_IP: '127.0.0.1'
|
||||
MODE: MINI
|
||||
ports:
|
||||
- "${OCEANBASE_VECTOR_PORT:-2881}:2881"
|
||||
|
||||
# Oracle vector database
|
||||
oracle:
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useDebounceFn } from 'ahooks'
|
||||
import {
|
||||
RiApps2Line,
|
||||
RiExchange2Line,
|
||||
RiFile4Line,
|
||||
RiMessage3Line,
|
||||
RiRobot3Line,
|
||||
} from '@remixicon/react'
|
||||
@@ -81,6 +82,8 @@ const Apps = () => {
|
||||
{ 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]' /> },
|
||||
]
|
||||
|
||||
|
||||
@@ -961,6 +961,12 @@ 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>
|
||||
@@ -1004,7 +1010,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"doc_form": "text_model"
|
||||
"doc_form": "text_model",
|
||||
"has_more": false,
|
||||
"limit": 20,
|
||||
"total": 9,
|
||||
"page": 1
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -1148,6 +1158,276 @@ 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'
|
||||
@@ -1694,4 +1974,4 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="pb-4" />
|
||||
<div className="pb-4" />
|
||||
@@ -961,6 +961,12 @@ 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>
|
||||
@@ -1004,7 +1010,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
|
||||
"error": null,
|
||||
"stopped_at": null
|
||||
}],
|
||||
"doc_form": "text_model"
|
||||
"doc_form": "text_model",
|
||||
"has_more": false,
|
||||
"limit": 20,
|
||||
"total": 9,
|
||||
"page": 1
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -1149,6 +1159,310 @@ 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'
|
||||
|
||||
@@ -212,7 +212,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
<div className='flex w-full'>
|
||||
<div className='system-md-semibold truncate text-text-secondary'>{appDetail.name}</div>
|
||||
</div>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{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 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>
|
||||
)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
/>
|
||||
<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.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 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>
|
||||
</div>
|
||||
{/* description */}
|
||||
@@ -242,7 +242,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
|
||||
<div className='system-xs-regular text-text-tertiary'>{appDetail.description}</div>
|
||||
)}
|
||||
{/* operations */}
|
||||
<div className='flex items-center gap-1 self-stretch'>
|
||||
<div className='flex flex-wrap items-center gap-1 self-stretch'>
|
||||
<Button
|
||||
size={'small'}
|
||||
variant={'secondary'}
|
||||
|
||||
@@ -651,7 +651,13 @@ const Configuration: FC = () => {
|
||||
|
||||
syncToPublishedConfig(config)
|
||||
setPublishedConfig(config)
|
||||
const retrievalConfig = getMultipleRetrievalConfig(modelConfig.dataset_configs, datasets, datasets, {
|
||||
const retrievalConfig = getMultipleRetrievalConfig({
|
||||
...modelConfig.dataset_configs,
|
||||
reranking_model: modelConfig.dataset_configs.reranking_model && {
|
||||
provider: modelConfig.dataset_configs.reranking_model.reranking_provider_name,
|
||||
model: modelConfig.dataset_configs.reranking_model.reranking_model_name,
|
||||
},
|
||||
}, datasets, datasets, {
|
||||
provider: currentRerankProvider?.provider,
|
||||
model: currentRerankModel?.model,
|
||||
})
|
||||
@@ -661,8 +667,8 @@ const Configuration: FC = () => {
|
||||
...retrievalConfig,
|
||||
...(retrievalConfig.reranking_model ? {
|
||||
reranking_model: {
|
||||
...retrievalConfig.reranking_model,
|
||||
reranking_provider_name: correctModelProvider(modelConfig.dataset_configs.reranking_model.reranking_provider_name),
|
||||
reranking_model_name: retrievalConfig.reranking_model.model,
|
||||
reranking_provider_name: correctModelProvider(retrievalConfig.reranking_model.provider),
|
||||
},
|
||||
} : {}),
|
||||
})
|
||||
|
||||
@@ -313,7 +313,7 @@ function AppPreview({ mode }: { mode: AppMode }) {
|
||||
'chat': {
|
||||
title: t('app.types.chatbot'),
|
||||
description: t('app.newApp.chatbotUserDescription'),
|
||||
link: 'https://docs.dify.ai/guides/application-orchestrate/conversation-application?fallback=true',
|
||||
link: 'https://docs.dify.ai/guides/application-orchestrate#application_type',
|
||||
},
|
||||
'advanced-chat': {
|
||||
title: t('app.types.advanced'),
|
||||
|
||||
@@ -78,7 +78,7 @@ const Sidebar = ({ isPanel }: Props) => {
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex grow flex-col',
|
||||
'flex w-full grow flex-col',
|
||||
isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg',
|
||||
)}>
|
||||
<div className={cn(
|
||||
|
||||
@@ -81,13 +81,13 @@ const Operation: FC<OperationProps> = ({
|
||||
const operationWidth = useMemo(() => {
|
||||
let width = 0
|
||||
if (!isOpeningStatement)
|
||||
width += 28
|
||||
width += 26
|
||||
if (!isOpeningStatement && showPromptLog)
|
||||
width += 102 + 8
|
||||
width += 28 + 8
|
||||
if (!isOpeningStatement && config?.text_to_speech?.enabled)
|
||||
width += 33
|
||||
width += 26
|
||||
if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled)
|
||||
width += 56 + 8
|
||||
width += 26
|
||||
if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement)
|
||||
width += 60 + 8
|
||||
if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement)
|
||||
@@ -140,7 +140,7 @@ const Operation: FC<OperationProps> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isOpeningStatement && config?.supportFeedback && onFeedback && (
|
||||
{!isOpeningStatement && config?.supportFeedback && !localFeedback?.rating && onFeedback && (
|
||||
<div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'>
|
||||
{!localFeedback?.rating && (
|
||||
<>
|
||||
@@ -152,6 +152,10 @@ const Operation: FC<OperationProps> = ({
|
||||
</ActionButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isOpeningStatement && config?.supportFeedback && localFeedback?.rating && onFeedback && (
|
||||
<div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
|
||||
{localFeedback?.rating === 'like' && (
|
||||
<ActionButton state={ActionButtonState.Active} onClick={() => handleFeedback(null)}>
|
||||
<RiThumbUpLine className='h-4 w-4' />
|
||||
|
||||
@@ -13,8 +13,9 @@ async function decodeBase64AndDecompress(base64String: string) {
|
||||
async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const inputs: Record<string, any> = {}
|
||||
const entriesArray = Array.from(urlParams.entries())
|
||||
await Promise.all(
|
||||
urlParams.entries().map(async ([key, value]) => {
|
||||
entriesArray.map(async ([key, value]) => {
|
||||
inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import path from 'node:path'
|
||||
import { access, appendFile, mkdir, open, readdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parseXml } from '@rgrove/parse-xml'
|
||||
import { camelCase, template } from 'lodash-es'
|
||||
|
||||
const __dirname = path.dirname(new URL(import.meta.url).pathname)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const generateDir = async (currentPath) => {
|
||||
try {
|
||||
@@ -105,7 +106,7 @@ const generateImageComponent = async (entry, pathList) => {
|
||||
}
|
||||
`.trim())
|
||||
|
||||
await writeFile(path.resolve(currentPath, `${fileName}.module.css`), `${componentCSSRender({ assetPath: path.join('~@/app/components/base/icons/assets', ...pathList.slice(2), entry) })}\n`)
|
||||
await writeFile(path.resolve(currentPath, `${fileName}.module.css`), `${componentCSSRender({ assetPath: path.posix.join('~@/app/components/base/icons/assets', ...pathList.slice(2), entry) })}\n`)
|
||||
|
||||
const componentRender = template(`
|
||||
// GENERATE BY script
|
||||
|
||||
@@ -27,6 +27,7 @@ import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
|
||||
import { Theme } from '@/types/app'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import cn from '@/utils/classnames'
|
||||
import SVGRenderer from './svg-gallery'
|
||||
|
||||
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
|
||||
const capitalizationLanguageNameMap: Record<string, string> = {
|
||||
@@ -65,12 +66,22 @@ const preprocessLaTeX = (content: string) => {
|
||||
if (typeof content !== 'string')
|
||||
return content
|
||||
|
||||
return flow([
|
||||
const codeBlockRegex = /```[\s\S]*?```/g
|
||||
const codeBlocks = content.match(codeBlockRegex) || []
|
||||
let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER')
|
||||
|
||||
processedContent = flow([
|
||||
(str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`),
|
||||
(str: string) => str.replace(/\\\[(.*?)\\\]/gs, (_, equation) => `$$${equation}$$`),
|
||||
(str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`),
|
||||
(str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`),
|
||||
])(content)
|
||||
])(processedContent)
|
||||
|
||||
codeBlocks.forEach((block) => {
|
||||
processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block)
|
||||
})
|
||||
|
||||
return processedContent
|
||||
}
|
||||
|
||||
const preprocessThinkTag = (content: string) => {
|
||||
@@ -136,14 +147,13 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) =>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// Attention: SVGRenderer has xss vulnerability
|
||||
// else if (language === 'svg' && isSVG) {
|
||||
// return (
|
||||
// <ErrorBoundary>
|
||||
// <SVGRenderer content={content} />
|
||||
// </ErrorBoundary>
|
||||
// )
|
||||
// }
|
||||
else if (language === 'svg' && isSVG) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SVGRenderer content={content} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
@@ -240,19 +250,11 @@ const Link = ({ node, ...props }: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeSVGTags(htmlString: string): string {
|
||||
return htmlString.replace(/(<svg[\s\S]*?>)([\s\S]*?)(<\/svg>)/gi, (match: string, openTag: string, innerContent: string, closeTag: string): string => {
|
||||
return openTag.replace(/</g, '<').replace(/>/g, '>')
|
||||
+ innerContent.replace(/</g, '<').replace(/>/g, '>')
|
||||
+ closeTag.replace(/</g, '<').replace(/>/g, '>')
|
||||
})
|
||||
}
|
||||
|
||||
export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
|
||||
const latexContent = flow([
|
||||
preprocessThinkTag,
|
||||
preprocessLaTeX,
|
||||
])(escapeSVGTags(props.content))
|
||||
])(props.content)
|
||||
|
||||
return (
|
||||
<div className={cn('markdown-body', '!text-text-primary', props.className)}>
|
||||
|
||||
@@ -9,7 +9,7 @@ export type HtmlContentProps = {
|
||||
|
||||
type IPopover = {
|
||||
className?: string
|
||||
htmlContent: React.ReactNode<HtmlContentProps>
|
||||
htmlContent: React.ReactNode
|
||||
popupClassName?: string
|
||||
trigger?: 'click' | 'hover'
|
||||
position?: 'bottom' | 'br' | 'bl'
|
||||
@@ -90,7 +90,7 @@ export default function CustomPopover({
|
||||
>
|
||||
{({ close }) => (
|
||||
<div
|
||||
className={cn('w-fit min-w-[130px] overflow-hidden rounded-lg bg-components-panel-bg shadow-lg ring-1 ring-black ring-opacity-5', popupClassName)}
|
||||
className={cn('w-fit min-w-[130px] overflow-hidden rounded-lg bg-components-panel-bg shadow-lg ring-1 ring-black/5', popupClassName)}
|
||||
{...(trigger !== 'hover'
|
||||
? {}
|
||||
: {
|
||||
@@ -99,7 +99,7 @@ export default function CustomPopover({
|
||||
})
|
||||
}
|
||||
>
|
||||
{cloneElement(htmlContent as React.ReactNode<HtmlContentProps>, {
|
||||
{cloneElement(htmlContent as React.ReactElement, {
|
||||
onClose: () => onMouseLeave(open),
|
||||
...(manualClose
|
||||
? {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Listbox, ListboxButton, ListboxOption, ListboxOptions, Transition } from '@headlessui/react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
|
||||
import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import Badge from '../badge/index'
|
||||
import { RiCheckLine } from '@remixicon/react'
|
||||
@@ -238,48 +238,40 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
)}
|
||||
|
||||
{!disabled && (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
|
||||
<ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
|
||||
{items.map((item: Item) => (
|
||||
<ListboxOption
|
||||
key={item.value}
|
||||
className={
|
||||
classNames(
|
||||
'relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-state-base-hover text-text-secondary',
|
||||
optionClassName,
|
||||
)
|
||||
}
|
||||
value={item}
|
||||
disabled={disabled}
|
||||
>
|
||||
{({ /* active, */ selected }) => (
|
||||
<>
|
||||
{renderOption
|
||||
? renderOption({ item, selected })
|
||||
: (<>
|
||||
<span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
|
||||
{selected && !hideChecked && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-text-accent',
|
||||
)}
|
||||
>
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>)}
|
||||
</>
|
||||
)}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
</Transition>
|
||||
<ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
|
||||
{items.map((item: Item) => (
|
||||
<ListboxOption
|
||||
key={item.value}
|
||||
className={
|
||||
classNames(
|
||||
'relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-state-base-hover text-text-secondary',
|
||||
optionClassName,
|
||||
)
|
||||
}
|
||||
value={item}
|
||||
disabled={disabled}
|
||||
>
|
||||
{({ /* active, */ selected }) => (
|
||||
<>
|
||||
{renderOption
|
||||
? renderOption({ item, selected })
|
||||
: (<>
|
||||
<span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
|
||||
{selected && !hideChecked && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-text-accent',
|
||||
)}
|
||||
>
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>)}
|
||||
</>
|
||||
)}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
)}
|
||||
</div>
|
||||
</Listbox>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
export const SVGRenderer = ({ content }: { content: string }) => {
|
||||
const svgRef = useRef<HTMLDivElement>(null)
|
||||
@@ -44,7 +45,7 @@ export const SVGRenderer = ({ content }: { content: string }) => {
|
||||
|
||||
svgRef.current.style.width = `${Math.min(originalWidth, 298)}px`
|
||||
|
||||
const rootElement = draw.svg(content)
|
||||
const rootElement = draw.svg(DOMPurify.sanitize(content))
|
||||
|
||||
rootElement.click(() => {
|
||||
setImagePreview(svgToDataURL(svgElement as Element))
|
||||
|
||||
@@ -103,13 +103,16 @@ const SegmentAdd: FC<ISegmentAddProps> = ({
|
||||
manualClose
|
||||
trigger='click'
|
||||
htmlContent={
|
||||
<button
|
||||
type='button'
|
||||
className='system-md-regular flex w-full items-center rounded-lg px-2 py-1.5 text-text-secondary'
|
||||
onClick={showBatchModal}
|
||||
>
|
||||
{t('datasetDocuments.list.action.batchAdd')}
|
||||
</button>
|
||||
// need to wrapper the button with div when manualClose is true
|
||||
<div className='w-full p-1'>
|
||||
<button
|
||||
type='button'
|
||||
className='system-md-regular flex w-full items-center rounded-lg px-2 py-1.5 text-text-secondary'
|
||||
onClick={showBatchModal}
|
||||
>
|
||||
{t('datasetDocuments.list.action.batchAdd')}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
btnElement={
|
||||
<div className='flex items-center justify-center' >
|
||||
|
||||
@@ -143,6 +143,7 @@ export const MarketplaceContextProvider = ({
|
||||
resetPlugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
cancelQueryPluginsWithDebounced,
|
||||
isLoading: isPluginsLoading,
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
@@ -209,12 +210,13 @@ export const MarketplaceContextProvider = ({
|
||||
|
||||
const handleQuery = useCallback((debounced?: boolean) => {
|
||||
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
||||
cancelQueryPluginsWithDebounced()
|
||||
handleQueryMarketplaceCollectionsAndPlugins()
|
||||
return
|
||||
}
|
||||
|
||||
handleQueryPlugins(debounced)
|
||||
}, [handleQueryMarketplaceCollectionsAndPlugins, handleQueryPlugins])
|
||||
}, [handleQueryMarketplaceCollectionsAndPlugins, handleQueryPlugins, cancelQueryPluginsWithDebounced])
|
||||
|
||||
const handleSearchPluginTextChange = useCallback((text: string) => {
|
||||
setSearchPluginText(text)
|
||||
|
||||
@@ -89,7 +89,7 @@ export const useMarketplacePlugins = () => {
|
||||
handleUpdatePlugins(pluginsSearchParams)
|
||||
}, [handleUpdatePlugins])
|
||||
|
||||
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
|
||||
const { run: queryPluginsWithDebounced, cancel: cancelQueryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
|
||||
handleUpdatePlugins(pluginsSearchParams)
|
||||
}, {
|
||||
wait: 500,
|
||||
@@ -101,6 +101,7 @@ export const useMarketplacePlugins = () => {
|
||||
resetPlugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
cancelQueryPluginsWithDebounced,
|
||||
isLoading: isPending,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +556,10 @@ export const FILE_STRUCT: Var[] = [
|
||||
variable: 'url',
|
||||
type: VarType.string,
|
||||
},
|
||||
{
|
||||
variable: 'related_id',
|
||||
type: VarType.string,
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_FILE_UPLOAD_SETTING = {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { FC } from 'react'
|
||||
import { createContext, useCallback, useEffect, useRef } from 'react'
|
||||
import { createDatasetsDetailStore } from './store'
|
||||
import type { CommonNodeType, Node } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
|
||||
type DatasetsDetailStoreApi = ReturnType<typeof createDatasetsDetailStore>
|
||||
|
||||
type DatasetsDetailContextType = DatasetsDetailStoreApi | undefined
|
||||
|
||||
export const DatasetsDetailContext = createContext<DatasetsDetailContextType>(undefined)
|
||||
|
||||
type DatasetsDetailProviderProps = {
|
||||
nodes: Node[]
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const DatasetsDetailProvider: FC<DatasetsDetailProviderProps> = ({
|
||||
nodes,
|
||||
children,
|
||||
}) => {
|
||||
const storeRef = useRef<DatasetsDetailStoreApi>()
|
||||
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createDatasetsDetailStore()
|
||||
|
||||
const updateDatasetsDetail = useCallback(async (datasetIds: string[]) => {
|
||||
const { data: datasetsDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasetIds } })
|
||||
if (datasetsDetail && datasetsDetail.length > 0)
|
||||
storeRef.current!.getState().updateDatasetsDetail(datasetsDetail)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!storeRef.current) return
|
||||
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
|
||||
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
|
||||
return Array.from(new Set([...acc, ...(node.data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids]))
|
||||
}, [])
|
||||
if (allDatasetIds.length === 0) return
|
||||
updateDatasetsDetail(allDatasetIds)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<DatasetsDetailContext.Provider value={storeRef.current!}>
|
||||
{children}
|
||||
</DatasetsDetailContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default DatasetsDetailProvider
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useContext } from 'react'
|
||||
import { createStore, useStore } from 'zustand'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { DatasetsDetailContext } from './provider'
|
||||
import produce from 'immer'
|
||||
|
||||
type DatasetsDetailStore = {
|
||||
datasetsDetail: Record<string, DataSet>
|
||||
updateDatasetsDetail: (datasetsDetail: DataSet[]) => void
|
||||
}
|
||||
|
||||
export const createDatasetsDetailStore = () => {
|
||||
return createStore<DatasetsDetailStore>((set, get) => ({
|
||||
datasetsDetail: {},
|
||||
updateDatasetsDetail: (datasets: DataSet[]) => {
|
||||
const oldDatasetsDetail = get().datasetsDetail
|
||||
const datasetsDetail = datasets.reduce<Record<string, DataSet>>((acc, dataset) => {
|
||||
acc[dataset.id] = dataset
|
||||
return acc
|
||||
}, {})
|
||||
// Merge new datasets detail into old one
|
||||
const newDatasetsDetail = produce(oldDatasetsDetail, (draft) => {
|
||||
Object.entries(datasetsDetail).forEach(([key, value]) => {
|
||||
draft[key] = value
|
||||
})
|
||||
})
|
||||
set({ datasetsDetail: newDatasetsDetail })
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export const useDatasetsDetailStore = <T>(selector: (state: DatasetsDetailStore) => T): T => {
|
||||
const store = useContext(DatasetsDetailContext)
|
||||
if (!store)
|
||||
throw new Error('Missing DatasetsDetailContext.Provider in the tree')
|
||||
|
||||
return useStore(store, selector)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ const Header: FC = () => {
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
|
||||
|
||||
const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
|
||||
if (handleCheckBeforePublish()) {
|
||||
if (await handleCheckBeforePublish()) {
|
||||
const res = await publishWorkflow({
|
||||
title: params?.title || '',
|
||||
releaseNotes: params?.releaseNotes || '',
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
Edge,
|
||||
Node,
|
||||
} from '../types'
|
||||
@@ -27,6 +29,10 @@ import { useGetLanguage } from '@/context/i18n'
|
||||
import type { AgentNodeType } from '../nodes/agent/types'
|
||||
import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import { canFindTool } from '@/utils'
|
||||
import { useDatasetsDetailStore } from '../datasets-detail-store/store'
|
||||
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
|
||||
export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -37,6 +43,24 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const { data: strategyProviders } = useStrategyProviders()
|
||||
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
|
||||
|
||||
const getCheckData = useCallback((data: CommonNodeType<{}>) => {
|
||||
let checkData = data
|
||||
if (data.type === BlockEnum.KnowledgeRetrieval) {
|
||||
const datasetIds = (data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids
|
||||
const _datasets = datasetIds.reduce<DataSet[]>((acc, id) => {
|
||||
if (datasetsDetail[id])
|
||||
acc.push(datasetsDetail[id])
|
||||
return acc
|
||||
}, [])
|
||||
checkData = {
|
||||
...data,
|
||||
_datasets,
|
||||
} as CommonNodeType<KnowledgeRetrievalNodeType>
|
||||
}
|
||||
return checkData
|
||||
}, [datasetsDetail])
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
@@ -75,7 +99,8 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
|
||||
if (node.type === CUSTOM_NODE) {
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t, moreDataForCheckValid)
|
||||
const checkData = getCheckData(node.data)
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(checkData, t, moreDataForCheckValid)
|
||||
|
||||
if (errorMessage || !validNodes.find(n => n.id === node.id)) {
|
||||
list.push({
|
||||
@@ -109,7 +134,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
|
||||
return list
|
||||
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders])
|
||||
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData])
|
||||
|
||||
return needWarningNodes
|
||||
}
|
||||
@@ -125,8 +150,31 @@ export const useChecklistBeforePublish = () => {
|
||||
const store = useStoreApi()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { data: strategyProviders } = useStrategyProviders()
|
||||
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
|
||||
const updateTime = useRef(0)
|
||||
|
||||
const handleCheckBeforePublish = useCallback(() => {
|
||||
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
|
||||
let checkData = data
|
||||
if (data.type === BlockEnum.KnowledgeRetrieval) {
|
||||
const datasetIds = (data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids
|
||||
const datasetsDetail = datasets.reduce<Record<string, DataSet>>((acc, dataset) => {
|
||||
acc[dataset.id] = dataset
|
||||
return acc
|
||||
}, {})
|
||||
const _datasets = datasetIds.reduce<DataSet[]>((acc, id) => {
|
||||
if (datasetsDetail[id])
|
||||
acc.push(datasetsDetail[id])
|
||||
return acc
|
||||
}, [])
|
||||
checkData = {
|
||||
...data,
|
||||
_datasets,
|
||||
} as CommonNodeType<KnowledgeRetrievalNodeType>
|
||||
}
|
||||
return checkData
|
||||
}, [])
|
||||
|
||||
const handleCheckBeforePublish = useCallback(async () => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
@@ -141,6 +189,24 @@ export const useChecklistBeforePublish = () => {
|
||||
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) })
|
||||
return false
|
||||
}
|
||||
// Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed
|
||||
const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
|
||||
const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => {
|
||||
return Array.from(new Set([...acc, ...(node.data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids]))
|
||||
}, [])
|
||||
let datasets: DataSet[] = []
|
||||
if (allDatasetIds.length > 0) {
|
||||
updateTime.current = updateTime.current + 1
|
||||
const currUpdateTime = updateTime.current
|
||||
const { data: datasetsDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: allDatasetIds } })
|
||||
if (datasetsDetail && datasetsDetail.length > 0) {
|
||||
// avoid old data to overwrite the new data
|
||||
if (currUpdateTime < updateTime.current)
|
||||
return false
|
||||
datasets = datasetsDetail
|
||||
updateDatasetsDetail(datasetsDetail)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
@@ -161,7 +227,8 @@ export const useChecklistBeforePublish = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { errorMessage } = nodesExtraData[node.data.type as BlockEnum].checkValid(node.data, t, moreDataForCheckValid)
|
||||
const checkData = getCheckData(node.data, datasets)
|
||||
const { errorMessage } = nodesExtraData[node.data.type as BlockEnum].checkValid(checkData, t, moreDataForCheckValid)
|
||||
|
||||
if (errorMessage) {
|
||||
notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` })
|
||||
@@ -185,7 +252,7 @@ export const useChecklistBeforePublish = () => {
|
||||
}
|
||||
|
||||
return true
|
||||
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders])
|
||||
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData])
|
||||
|
||||
return {
|
||||
handleCheckBeforePublish,
|
||||
|
||||
@@ -99,6 +99,7 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import { fetchFileUploadConfig } from '@/service/common'
|
||||
import DatasetsDetailProvider from './datasets-detail-store/provider'
|
||||
|
||||
const nodeTypes = {
|
||||
[CUSTOM_NODE]: CustomNode,
|
||||
@@ -448,11 +449,13 @@ const WorkflowWrap = memo(() => {
|
||||
nodes={nodesData}
|
||||
edges={edgesData} >
|
||||
<FeaturesProvider features={initialFeatures}>
|
||||
<Workflow
|
||||
nodes={nodesData}
|
||||
edges={edgesData}
|
||||
viewport={data?.graph.viewport}
|
||||
/>
|
||||
<DatasetsDetailProvider nodes={nodesData}>
|
||||
<Workflow
|
||||
nodes={nodesData}
|
||||
edges={edgesData}
|
||||
viewport={data?.graph.viewport}
|
||||
/>
|
||||
</DatasetsDetailProvider>
|
||||
</FeaturesProvider>
|
||||
</WorkflowHistoryProvider>
|
||||
</ReactFlowProvider>
|
||||
|
||||
@@ -100,5 +100,5 @@ export const TRANSFER_METHOD = [
|
||||
{ value: TransferMethod.remote_url, i18nKey: 'url' },
|
||||
]
|
||||
|
||||
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method']
|
||||
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method', 'related_id']
|
||||
export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method')
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ const ConditionVarSelector = ({
|
||||
crossAxis: 0,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => onOpenChange(!open)}>
|
||||
<div className="cursor-pointer">
|
||||
<PortalToFollowElemTrigger asChild onClick={() => onOpenChange(!open)}>
|
||||
<div className="w-full cursor-pointer">
|
||||
<VariableTag
|
||||
valueSelector={valueSelector}
|
||||
varType={varType}
|
||||
|
||||
@@ -78,11 +78,11 @@ const ConditionValue = ({
|
||||
<div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1'>
|
||||
{!isEnvVar && !isChatVar && <Variable02 className={cn('mr-1 h-3.5 w-3.5 shrink-0 text-text-accent', isException && 'text-text-warning')} />}
|
||||
{isEnvVar && <Env className='mr-1 h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='h-3.5 w-3.5 text-util-colors-teal-teal-700' />}
|
||||
{isChatVar && <BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' />}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'ml-0.5 shrink-0 truncate text-xs font-medium text-text-accent',
|
||||
'ml-0.5 shrink-[2] truncate text-xs font-medium text-text-accent',
|
||||
!notHasValue && 'max-w-[70px]',
|
||||
isException && 'text-text-warning',
|
||||
)}
|
||||
@@ -98,7 +98,7 @@ const ConditionValue = ({
|
||||
</div>
|
||||
{
|
||||
!notHasValue && (
|
||||
<div className='truncate text-xs text-text-secondary' title={formatValue}>{isSelect ? selectName : formatValue}</div>
|
||||
<div className='shrink-[3] truncate text-xs text-text-secondary' title={formatValue}>{isSelect ? selectName : formatValue}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
import { type FC, useEffect, useRef, useState } from 'react'
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
import type { KnowledgeRetrievalNodeType } from './types'
|
||||
import { Folder } from '@/app/components/base/icons/src/vender/solid/files'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { useDatasetsDetailStore } from '../../datasets-detail-store/store'
|
||||
|
||||
const Node: FC<NodeProps<KnowledgeRetrievalNodeType>> = ({
|
||||
data,
|
||||
}) => {
|
||||
const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([])
|
||||
const updateTime = useRef(0)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
updateTime.current = updateTime.current + 1
|
||||
const currUpdateTime = updateTime.current
|
||||
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
|
||||
|
||||
if (data.dataset_ids?.length > 0) {
|
||||
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: data.dataset_ids } })
|
||||
// avoid old data overwrite new data
|
||||
if (currUpdateTime < updateTime.current)
|
||||
return
|
||||
setSelectedDatasets(dataSetsWithDetail)
|
||||
}
|
||||
else {
|
||||
setSelectedDatasets([])
|
||||
}
|
||||
})()
|
||||
}, [data.dataset_ids])
|
||||
useEffect(() => {
|
||||
if (data.dataset_ids?.length > 0) {
|
||||
const dataSetsWithDetail = data.dataset_ids.reduce<DataSet[]>((acc, id) => {
|
||||
if (datasetsDetail[id])
|
||||
acc.push(datasetsDetail[id])
|
||||
return acc
|
||||
}, [])
|
||||
setSelectedDatasets(dataSetsWithDetail)
|
||||
}
|
||||
else {
|
||||
setSelectedDatasets([])
|
||||
}
|
||||
}, [data.dataset_ids, datasetsDetail])
|
||||
|
||||
if (!selectedDatasets.length)
|
||||
return null
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
DataSet,
|
||||
MetadataInDoc,
|
||||
RerankingModeEnum,
|
||||
WeightedScoreEnum,
|
||||
} from '@/models/datasets'
|
||||
|
||||
export type MultipleRetrievalConfig = {
|
||||
@@ -21,6 +22,7 @@ export type MultipleRetrievalConfig = {
|
||||
}
|
||||
reranking_mode?: RerankingModeEnum
|
||||
weights?: {
|
||||
weight_type: WeightedScoreEnum
|
||||
vector_setting: {
|
||||
vector_weight: number
|
||||
embedding_provider_name: string
|
||||
|
||||
@@ -41,6 +41,7 @@ import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-s
|
||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import { useDatasetsDetailStore } from '../../datasets-detail-store/store'
|
||||
|
||||
const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
@@ -49,6 +50,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
const startNode = getBeforeNodesInSameBranch(id).find(node => node.data.type === BlockEnum.Start)
|
||||
const startNodeId = startNode?.id
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<KnowledgeRetrievalNodeType>(id, payload)
|
||||
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
|
||||
|
||||
const inputRef = useRef(inputs)
|
||||
|
||||
@@ -218,15 +220,12 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
(async () => {
|
||||
const inputs = inputRef.current
|
||||
const datasetIds = inputs.dataset_ids
|
||||
let _datasets = selectedDatasets
|
||||
if (datasetIds?.length > 0) {
|
||||
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasetIds } as any })
|
||||
_datasets = dataSetsWithDetail
|
||||
setSelectedDatasets(dataSetsWithDetail)
|
||||
}
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.dataset_ids = datasetIds
|
||||
draft._datasets = _datasets
|
||||
})
|
||||
setInputs(newInputs)
|
||||
setSelectedDatasetsLoaded(true)
|
||||
@@ -256,7 +255,6 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
} = getSelectedDatasetsMode(newDatasets)
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.dataset_ids = newDatasets.map(d => d.id)
|
||||
draft._datasets = newDatasets
|
||||
|
||||
if (payload.retrieval_mode === RETRIEVE_TYPE.multiWay && newDatasets.length > 0) {
|
||||
const multipleRetrievalConfig = draft.multiple_retrieval_config
|
||||
@@ -266,6 +264,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
})
|
||||
}
|
||||
})
|
||||
updateDatasetsDetail(newDatasets)
|
||||
setInputs(newInputs)
|
||||
setSelectedDatasets(newDatasets)
|
||||
|
||||
@@ -275,7 +274,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
|| allExternal
|
||||
)
|
||||
setRerankModelOpen(true)
|
||||
}, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel, currentRerankProvider])
|
||||
}, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel, currentRerankProvider, updateDatasetsDetail])
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
return varPayload.type === VarType.string
|
||||
|
||||
@@ -83,7 +83,7 @@ export const TRANSFER_METHOD = [
|
||||
{ value: TransferMethod.remote_url, i18nKey: 'url' },
|
||||
]
|
||||
|
||||
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method']
|
||||
export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method', 'related_id']
|
||||
export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method')
|
||||
|
||||
export default nodeDefault
|
||||
|
||||
@@ -22,16 +22,33 @@ const nodeDefault: NodeDefault<VariableAssignerNodeType> = {
|
||||
},
|
||||
checkValid(payload: VariableAssignerNodeType, t: any) {
|
||||
let errorMessages = ''
|
||||
const { variables } = payload
|
||||
if (!variables || variables.length === 0)
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.variableAssigner.title`) })
|
||||
if (!errorMessages) {
|
||||
const { variables, advanced_settings } = payload
|
||||
const { group_enabled = false, groups = [] } = advanced_settings || {}
|
||||
// enable group
|
||||
const validateVariables = (variables: any[], field: string) => {
|
||||
variables.forEach((variable) => {
|
||||
if (!variable || variable.length === 0)
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.errorMsg.fields.variableValue`) })
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(field) })
|
||||
})
|
||||
}
|
||||
|
||||
if (group_enabled) {
|
||||
if (!groups || groups.length === 0) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.variableAssigner.title`) })
|
||||
}
|
||||
else if (!errorMessages) {
|
||||
groups.forEach((group) => {
|
||||
validateVariables(group.variables || [], `${i18nPrefix}.errorMsg.fields.variableValue`)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!variables || variables.length === 0)
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.variableAssigner.title`) })
|
||||
else if (!errorMessages)
|
||||
validateVariables(variables, `${i18nPrefix}.errorMsg.fields.variableValue`)
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
|
||||
@@ -339,6 +339,7 @@ export type RunFile = {
|
||||
transfer_method: TransferMethod[]
|
||||
url?: string
|
||||
upload_file_id?: string
|
||||
related_id?: string
|
||||
}
|
||||
|
||||
export type WorkflowRunningData = {
|
||||
|
||||
@@ -70,6 +70,7 @@ const translation = {
|
||||
noHitHistory: 'ヒット履歴はありません',
|
||||
},
|
||||
hitHistoryTable: {
|
||||
question: '質問',
|
||||
query: 'クエリ',
|
||||
match: '一致',
|
||||
response: '応答',
|
||||
|
||||
@@ -26,10 +26,9 @@ const translation = {
|
||||
appDeleteFailed: 'アプリの削除に失敗しました',
|
||||
join: 'コミュニティに参加する',
|
||||
communityIntro:
|
||||
'さまざまなチャンネルでチームメンバーや貢献者、開発者と議論します。',
|
||||
'さまざまなチャンネルでチームメンバーや貢献者、開発者と議論します。',
|
||||
roadmap: 'ロードマップを見る',
|
||||
newApp: {
|
||||
// this comment is to recreate PR
|
||||
startFromBlank: '最初から作成',
|
||||
startFromTemplate: 'テンプレートから作成',
|
||||
captionAppType: 'どのタイプのアプリを作成しますか?',
|
||||
|
||||
@@ -101,18 +101,22 @@ const translation = {
|
||||
plans: {
|
||||
sandbox: {
|
||||
name: 'Sandbox',
|
||||
for: '核心能力的免费试用',
|
||||
description: 'コア機能を無料で試す',
|
||||
},
|
||||
professional: {
|
||||
name: 'Professional',
|
||||
for: '核心能力的免费试用',
|
||||
description: '独立した開発者/小規模チーム向け',
|
||||
},
|
||||
team: {
|
||||
name: 'Team',
|
||||
for: '核心能力的免费试用',
|
||||
description: '中規模チーム向け',
|
||||
},
|
||||
community: {
|
||||
name: 'Community',
|
||||
for: '核心能力的免费试用',
|
||||
description: '個人ユーザー、小規模チーム、または非営利プロジェクト向け',
|
||||
price: '無料',
|
||||
btnText: 'コミュニティを始めましょう',
|
||||
@@ -125,6 +129,7 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
name: 'Premium',
|
||||
for: '核心能力的免费试用',
|
||||
description: '中規模の組織やチーム向け',
|
||||
price: 'スケーラブル',
|
||||
priceTip: 'クラウドマーケットプレイスに基づく',
|
||||
@@ -140,6 +145,7 @@ const translation = {
|
||||
},
|
||||
enterprise: {
|
||||
name: 'Enterprise',
|
||||
for: '核心能力的免费试用',
|
||||
description: 'エンタープライズグレードのセキュリティ、コンプライアンス、拡張性、制御、およびより高度な機能を必要とする企業向け',
|
||||
price: 'カスタム',
|
||||
priceTip: '年間契約のみ',
|
||||
|
||||
@@ -26,6 +26,8 @@ const translation = {
|
||||
lineBreak: '改行',
|
||||
sure: '確認済み',
|
||||
download: 'ダウンロード',
|
||||
downloadSuccess: '下载完毕',
|
||||
downloadFailed: '下载失败,请稍后重试。',
|
||||
delete: '削除',
|
||||
settings: '設定',
|
||||
setup: 'セットアップ',
|
||||
@@ -152,12 +154,22 @@ const translation = {
|
||||
workspace: 'ワークスペース',
|
||||
createWorkspace: 'ワークスペースを作成',
|
||||
helpCenter: 'ヘルプ',
|
||||
support: '支持',
|
||||
compliance: '合规',
|
||||
communityFeedback: 'フィードバック',
|
||||
roadmap: 'ロードマップ',
|
||||
community: 'コミュニティ',
|
||||
about: 'Difyについて',
|
||||
logout: 'ログアウト',
|
||||
},
|
||||
compliance: {
|
||||
soc2Type1: 'SOC 2 Type I Report',
|
||||
soc2Type2: 'SOC 2 Type II Report',
|
||||
iso27001: 'ISO 27001:2022 Certification',
|
||||
gdpr: 'GDPR DPA',
|
||||
sandboxUpgradeTooltip: '仅适用于 Professional 或 Team 版计划。',
|
||||
professionalUpgradeTooltip: '仅适用于 Team 版计划或以上。',
|
||||
},
|
||||
settings: {
|
||||
accountGroup: 'アカウント',
|
||||
workplaceGroup: 'ワークスペース',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const translation = {
|
||||
custom: 'カスタマイズ',
|
||||
upgradeTip: {
|
||||
title: '升级您的计划',
|
||||
des: '升级您的计划来定制您的品牌。',
|
||||
prefix: 'プランをアップグレードして',
|
||||
suffix: 'ブランドをカスタマイズしましょう。',
|
||||
},
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
const translation = {
|
||||
steps: {
|
||||
header: {
|
||||
creation: 'ナレッジの作成',
|
||||
update: 'データの追加',
|
||||
fallbackRoute: '知識',
|
||||
fallbackRoute: 'ナレッジベース',
|
||||
},
|
||||
one: 'データソース',
|
||||
two: 'テキスト進行中',
|
||||
three: '実行と完成',
|
||||
},
|
||||
error: {
|
||||
unavailable: 'このナレッジは利用できません',
|
||||
unavailable: 'このナレッジベースは利用できません',
|
||||
},
|
||||
firecrawl: {
|
||||
configFirecrawl: '🔥Firecrawlの設定',
|
||||
@@ -50,11 +48,11 @@ const translation = {
|
||||
connect: '接続する',
|
||||
cancel: 'キャンセル',
|
||||
button: '次へ',
|
||||
emptyDatasetCreation: '空のナレッジを作成します',
|
||||
emptyDatasetCreation: '空のナレッジベースを作成します',
|
||||
modal: {
|
||||
title: '空のナレッジを作成',
|
||||
tip: '空のナレッジにはドキュメントが含まれず、いつでもドキュメントをアップロードできます。',
|
||||
input: 'ナレッジ名',
|
||||
title: '空のナレッジベースを作成',
|
||||
tip: '空のナレッジベースにはドキュメントが含まれず、いつでもドキュメントをアップロードできます。',
|
||||
input: 'ナレッジベースの名称',
|
||||
placeholder: '入力してください',
|
||||
nameNotEmpty: '名前は空にできません',
|
||||
nameLengthInvalid: '名前は1〜40文字である必要があります',
|
||||
@@ -63,13 +61,23 @@ const translation = {
|
||||
failed: '作成に失敗しました',
|
||||
},
|
||||
website: {
|
||||
chooseProvider: 'プロバイダーを選択する',
|
||||
fireCrawlNotConfigured: 'Firecrawlが設定されていません',
|
||||
fireCrawlNotConfiguredDescription: 'Firecrawl を使用するには、Firecrawl の API キーを設定してください。',
|
||||
jinaReaderNotConfigured: 'Jina Reader が設定されていません',
|
||||
jinaReaderNotConfiguredDescription: '無料のAPIキーを入力して、Jina Readerを設定します。',
|
||||
configure: '設定',
|
||||
configureFirecrawl: '配置 Firecrawl',
|
||||
configureJinaReader: '配置 Jina Reader',
|
||||
run: '実行',
|
||||
firecrawlTitle: '🔥Firecrawlを使っでウエブコンテンツを抽出',
|
||||
firecrawlDoc: 'Firecrawlドキュメント',
|
||||
firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website',
|
||||
jinaReaderTitle: 'サイト全体をMarkdownに変換する',
|
||||
jinaReaderDoc: 'Jina Readerの詳細',
|
||||
jinaReaderDocLink: 'https://jina.ai/reader',
|
||||
useSitemap: 'sitemap(サイトマップ)を使用する',
|
||||
useSitemapTooltip: 'サイトマップに沿ってサイトをクロールします。そうでない場合、Jina Readerはページの関連性に基づいて繰り返しクロールし、ページ数は少なくなりますが、高品質のページが得られます。',
|
||||
options: 'オプション',
|
||||
crawlSubPage: 'サブページをクロールする',
|
||||
limit: '制限',
|
||||
@@ -85,14 +93,6 @@ const translation = {
|
||||
scrapTimeInfo: '{{time}} 秒以内に合計 {{total}} ページをスクレイピングしました',
|
||||
preview: 'プレビュー',
|
||||
maxDepthTooltip: '入力されたURLを基にしたクローリング作業での設定可能な最大深度について説明します。深度0は入力されたURL自体のページを対象としたスクレイピングを意味します。深度1では、元のURLの直下にあるページ(URLに続く最初の"/"以降の内容)もスクレイピングの対象になります。この深度は指定した数値まで増加させることができ、それに応じてスクレイピングの範囲も広がっていきます。',
|
||||
jinaReaderDocLink: 'https://jina.ai/reader',
|
||||
useSitemap: 'サイトマップを使用する',
|
||||
jinaReaderNotConfigured: 'Jina Reader が設定されていません',
|
||||
jinaReaderDoc: 'Jina Readerの詳細',
|
||||
jinaReaderTitle: 'サイト全体をMarkdownに変換する',
|
||||
chooseProvider: 'プロバイダーを選択する',
|
||||
jinaReaderNotConfiguredDescription: '無料のAPIキーを入力してJina Readerを設定します。',
|
||||
useSitemapTooltip: 'サイトマップに沿ってサイトをクロールします。そうでない場合、Jina Readerはページの関連性に基づいて繰り返しクロールし、ページ数は少なくなりますが、高品質のページが得られます。',
|
||||
},
|
||||
},
|
||||
stepTwo: {
|
||||
@@ -112,7 +112,7 @@ const translation = {
|
||||
fullDoc: '全文',
|
||||
fullDocTip: 'ドキュメント全体を親チャンクとして使用し、直接検索します。パフォーマンス上の理由から、10000トークンを超えるテキストは自動的に切り捨てられます。',
|
||||
separator: 'チャンク識別子',
|
||||
separatorPlaceholder: '例えば改行(\\\\n)や特殊なセパレータ(例:「***」)',
|
||||
separatorPlaceholder: '例えば改行(\\n\\n)や特殊なセパレータ(例:「***」)',
|
||||
maxLength: '最大チャンク長',
|
||||
overlap: 'チャンクのオーバーラップ',
|
||||
overlapTip: 'チャンクのオーバーラップを設定することで、それらの間の意味的な関連性を維持し、検索効果を向上させることができます。最大チャンクサイズの10%〜25%を設定することをおすすめします。',
|
||||
@@ -155,7 +155,7 @@ const translation = {
|
||||
sideTipTitle: 'なぜチャンクと前処理が必要なのか',
|
||||
sideTipP1: 'テキストデータを処理する際、チャンクとクリーニングは2つの重要な前処理ステップです。',
|
||||
sideTipP2: 'セグメンテーションは長いテキストを段落に分割し、モデルがより理解しやすくします。これにより、モデルの結果の品質と関連性が向上します。',
|
||||
sideTipP3: 'クリーニングは不要な文字や書式を削除し、ナレッジをよりクリーンで解析しやすいものにします。',
|
||||
sideTipP3: 'クリーニングは不要な文字や書式を削除し、ナレッジベースをよりクリーンで解析しやすいものにします。',
|
||||
sideTipP4: '適切なチャンクとクリーニングはモデルのパフォーマンスを向上させ、より正確で価値のある結果を提供します。',
|
||||
previewTitle: 'プレビュー',
|
||||
previewTitleButton: 'プレビュー',
|
||||
@@ -165,7 +165,7 @@ const translation = {
|
||||
characters: '文字',
|
||||
indexSettingTip: 'インデックス方法を変更するには、',
|
||||
retrievalSettingTip: '検索方法を変更するには、',
|
||||
datasetSettingLink: 'ナレッジ設定',
|
||||
datasetSettingLink: 'ナレッジベース設定',
|
||||
separatorTip: '区切り文字は、テキストを区切るために使用される文字です。\\n\\n と \\n は、段落と行を区切るために一般的に使用される区切り記号です。カンマ (\\n\\n,\\n) と組み合わせると、最大チャンク長を超えると、段落は行で区切られます。自分で定義した特別な区切り文字を使用することもできます(例:***)。',
|
||||
maxLengthCheck: 'チャンクの最大長は {{limit}} 未満にする必要があります',
|
||||
previewChunkTip: 'プレビューを読み込むには、左側の \'チャンクをプレビュー\' ボタンをクリックしてください',
|
||||
@@ -179,17 +179,17 @@ const translation = {
|
||||
parentChildChunkDelimiterTip: '区切り文字とは、テキストを分割するために使用される文字です。\\n は、親チャンクを小さな子チャンクに分割する際におすすめです。独自の区切り文字も使用できます。',
|
||||
},
|
||||
stepThree: {
|
||||
creationTitle: '🎉 ナレッジが作成されました',
|
||||
creationContent: 'ナレッジの名前は自動的に設定されましたが、いつでも変更できます。',
|
||||
label: 'ナレッジ名',
|
||||
creationTitle: '🎉 ナレッジベースが作成されました',
|
||||
creationContent: 'ナレッジベースの名前は自動的に設定されましたが、自由に変更できます。',
|
||||
label: 'ナレッジベース名',
|
||||
additionTitle: '🎉 ドキュメントがアップロードされました',
|
||||
additionP1: 'ドキュメントはナレッジにアップロードされました',
|
||||
additionP2: '、ナレッジのドキュメントリストで見つけることができます。',
|
||||
additionP1: 'ドキュメントはナレッジベースにアップロードされました',
|
||||
additionP2: '、ナレッジベースのドキュメントリストで見つけることができます。',
|
||||
stop: '処理を停止',
|
||||
resume: '処理を再開',
|
||||
navTo: 'ドキュメントに移動',
|
||||
sideTipTitle: '次は何ですか',
|
||||
sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立したChatGPTインデックスプラグインとしてリリースすることもできます。',
|
||||
sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジベースをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立したChatGPTインデックスプラグインとしてリリースすることもできます。',
|
||||
modelTitle: '埋め込みを停止してもよろしいですか?',
|
||||
modelContent: '後で処理を再開する必要がある場合は、中断した場所から続行します。',
|
||||
modelButtonConfirm: '確認',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const translation = {
|
||||
list: {
|
||||
title: 'ドキュメント',
|
||||
desc: 'ナレッジのすべてのファイルがここに表示され、ナレッジ全体がDifyの引用やチャットプラグインを介してリンクされるか、インデックス化されることができます。',
|
||||
desc: 'すべてのファイルがここに表示され、ナレッジベース全体がDifyの引用やチャットプラグインを介してリンクされるか、インデックス化されることができます。',
|
||||
learnMore: '詳細はこちら',
|
||||
addFile: 'ファイルを追加',
|
||||
addPages: 'ページを追加',
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const translation = {
|
||||
title: 'ナレッジの設定',
|
||||
desc: 'ここではナレッジのプロパティと動作方法を変更できます。',
|
||||
title: 'ナレッジベースの設定',
|
||||
desc: 'ここではナレッジベースのプロパティと動作方法を変更できます。',
|
||||
form: {
|
||||
name: 'ナレッジ名',
|
||||
namePlaceholder: 'ナレッジ名を入力してください',
|
||||
name: 'ナレッジベース名',
|
||||
namePlaceholder: 'ナレッジベース名を入力してください',
|
||||
nameError: '名前は空にできません',
|
||||
desc: 'ナレッジの説明',
|
||||
descInfo: 'ナレッジの内容を概説するための明確なテキストの説明を書いてください。この説明は、複数のナレッジから推論を選択する際の基準として使用されます。',
|
||||
desc: 'ナレッジベースの説明',
|
||||
descInfo: 'ナレッジベースの内容を概説するための明確なテキストの説明を書いてください。この説明は、複数のナレッジから推論を選択する際の基準として使用されます。',
|
||||
descPlaceholder: 'このデータセットの内容を記述してください。詳細に記述することで、AIがデータセットの内容に迅速にアクセスできるようになります。空欄の場合、LangGeniusはデフォルトの検索方法を使用します。',
|
||||
helpText: '適切なデータセットの説明を作成する方法を学びましょう。',
|
||||
descWrite: '良いナレッジの説明の書き方を学ぶ。',
|
||||
descWrite: '良いナレッジベースの説明の書き方を学ぶ。',
|
||||
permissions: '権限',
|
||||
permissionsOnlyMe: '自分のみ',
|
||||
permissionsAllMember: 'すべてのチームメンバー',
|
||||
@@ -26,14 +26,15 @@ const translation = {
|
||||
embeddingModelTipLink: '設定',
|
||||
retrievalSetting: {
|
||||
title: '検索設定',
|
||||
method: '検索方法',
|
||||
learnMore: '詳細はこちら',
|
||||
description: ' 検索方法についての詳細',
|
||||
longDescription: ' 検索方法についての詳細については、いつでもナレッジの設定で変更できます。',
|
||||
longDescription: ' 検索方法についての詳細については、いつでもナレッジベースの設定で変更できます。',
|
||||
},
|
||||
save: '保存',
|
||||
externalKnowledgeID: '外部ナレッジID',
|
||||
externalKnowledgeID: '外部ナレッジベースID',
|
||||
retrievalSettings: '取得設定',
|
||||
externalKnowledgeAPI: '外部ナレッジAPI',
|
||||
externalKnowledgeAPI: '外部ナレッジベースAPI',
|
||||
indexMethodChangeToEconomyDisabledTip: 'HQからECOへのダウングレードはできません。',
|
||||
},
|
||||
}
|
||||
|
||||
+82
-34
@@ -1,7 +1,7 @@
|
||||
const translation = {
|
||||
knowledge: 'ナレッジ',
|
||||
knowledge: 'ナレッジベース',
|
||||
chunkingMode: {
|
||||
general: '一般',
|
||||
general: '汎用',
|
||||
parentChild: '親子',
|
||||
},
|
||||
parentMode: {
|
||||
@@ -10,33 +10,33 @@ const translation = {
|
||||
},
|
||||
externalTag: '外部',
|
||||
externalAPI: '外部API',
|
||||
externalAPIPanelTitle: '外部ナレッジ連携API',
|
||||
externalKnowledgeId: '外部ナレッジID',
|
||||
externalKnowledgeName: '外部ナレッジ名',
|
||||
externalKnowledgeDescription: 'ナレッジの説明',
|
||||
externalKnowledgeIdPlaceholder: 'ナレッジIDを入力',
|
||||
externalAPIPanelTitle: '外部ナレッジベース連携API',
|
||||
externalKnowledgeId: '外部ナレッジベースID',
|
||||
externalKnowledgeName: '外部ナレッジベース名',
|
||||
externalKnowledgeDescription: 'ナレッジベースの説明',
|
||||
externalKnowledgeIdPlaceholder: 'ナレッジベースIDを入力',
|
||||
externalKnowledgeNamePlaceholder: 'ナレッジベース名を入力',
|
||||
externalKnowledgeDescriptionPlaceholder: 'このナレッジベースの説明(任意)',
|
||||
learnHowToWriteGoodKnowledgeDescription: '効果的なナレッジの説明の書き方',
|
||||
externalAPIPanelDescription: '外部ナレッジ連携APIは、Dify外のナレッジベースと連携し、そこからナレッジを取得するために使用します。',
|
||||
externalAPIPanelDocumentation: '外部ナレッジ連携APIの作成方法',
|
||||
learnHowToWriteGoodKnowledgeDescription: '効果的なナレッジベースの説明の書き方',
|
||||
externalAPIPanelDescription: '外部ナレッジベース連携APIは、Dify外のナレッジベースと連携し、そこからナレッジベースを取得するために使用します。',
|
||||
externalAPIPanelDocumentation: '外部ナレッジベース連携APIの作成方法',
|
||||
localDocs: 'ローカルドキュメント',
|
||||
documentCount: ' ドキュメント',
|
||||
wordCount: ' k 単語',
|
||||
appCount: ' リンクされたアプリ',
|
||||
createDataset: 'ナレッジを作成',
|
||||
createNewExternalAPI: '新しい外部ナレッジ連携APIを作成',
|
||||
noExternalKnowledge: '外部ナレッジ連携APIがありません。ここをクリックして作成してください',
|
||||
createExternalAPI: '外部ナレッジ連携APIを追加',
|
||||
editExternalAPIFormTitle: '外部ナレッジ連携APIを編集',
|
||||
editExternalAPITooltipTitle: '連携中のナレッジ',
|
||||
createDataset: 'ナレッジベースを作成',
|
||||
createNewExternalAPI: '新しい外部ナレッジベース連携APIを作成',
|
||||
noExternalKnowledge: '外部ナレッジベース連携APIがありません。ここをクリックして作成してください',
|
||||
createExternalAPI: '外部ナレッジベース連携APIを追加',
|
||||
editExternalAPIFormTitle: '外部ナレッジベース連携APIを編集',
|
||||
editExternalAPITooltipTitle: '連携中のナレッジベース',
|
||||
editExternalAPIConfirmWarningContent: {
|
||||
front: 'この外部ナレッジ連携APIは',
|
||||
end: '件の外部ナレッジと連携しており、この変更はすべてに適用されます。変更を保存しますか?',
|
||||
front: 'この外部ナレッジベース連携APIは',
|
||||
end: '件の外部ナレッジベースと連携しており、この変更はすべてに適用されます。変更を保存しますか?',
|
||||
},
|
||||
editExternalAPIFormWarning: {
|
||||
front: 'この外部APIは',
|
||||
end: '件の外部ナレッジと連携しています',
|
||||
end: '件の外部ナレッジベースと連携しています',
|
||||
},
|
||||
deleteExternalAPIConfirmWarningContent: {
|
||||
title: {
|
||||
@@ -44,13 +44,13 @@ const translation = {
|
||||
end: 'しますか?',
|
||||
},
|
||||
content: {
|
||||
front: 'この外部ナレッジ連携APIは',
|
||||
end: '件の外部ナレッジと連携しています。このAPIを削除すると、すべて無効になります。このAPIを削除しますか?',
|
||||
front: 'この外部ナレッジベース連携APIは',
|
||||
end: '件の外部ナレッジベースと連携しています。このAPIを削除すると、すべて無効になります。このAPIを削除しますか?',
|
||||
},
|
||||
noConnectionContent: 'このAPIを削除しますか?',
|
||||
},
|
||||
selectExternalKnowledgeAPI: {
|
||||
placeholder: '外部ナレッジ連携APIを選択',
|
||||
placeholder: '外部ナレッジベース連携APIを選択',
|
||||
},
|
||||
connectDataset: '外部ナレッジベースと連携',
|
||||
connectDatasetIntro: {
|
||||
@@ -58,7 +58,7 @@ const translation = {
|
||||
content: {
|
||||
front: '外部ナレッジベースと連携するには、まず外部APIを作成する必要があります。以下の手順を参照し、',
|
||||
link: '外部APIの作成方法',
|
||||
end: 'をご確認ください。次に、対応するナレッジIDを左側のフォームに入力してください。すべての情報が正しければ、連携ボタンをクリックすると、自動的にナレッジベースの検索テストに移動します。',
|
||||
end: 'をご確認ください。次に、対応するナレッジベースIDを左側のフォームに入力してください。すべての情報が正しければ、連携ボタンをクリックすると、自動的にナレッジベースの検索テストに移動します。',
|
||||
},
|
||||
learnMore: '詳細はこちら',
|
||||
},
|
||||
@@ -70,14 +70,14 @@ const translation = {
|
||||
helper5: 'をよくお読みください。',
|
||||
},
|
||||
createDatasetIntro: '独自のテキストデータをインポートするか、LLMコンテキストの強化のためにWebhookを介してリアルタイムでデータを書き込むことができます。',
|
||||
deleteDatasetConfirmTitle: 'このナレッジを削除しますか?',
|
||||
deleteDatasetConfirmTitle: 'このナレッジベースを削除しますか?',
|
||||
deleteDatasetConfirmContent:
|
||||
'ナレッジを削除すると元に戻すことはできません。ユーザーはもはやあなた様のナレッジにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。',
|
||||
datasetUsedByApp: 'このナレッジは一部のアプリによって使用されています。アプリはこのナレッジを使用できなくなり、すべてのプロンプト設定とログは永久に削除されます。',
|
||||
datasetDeleted: 'ナレッジが削除されました',
|
||||
datasetDeleteFailed: 'ナレッジの削除に失敗しました',
|
||||
'ナレッジベースを削除すると元に戻すことはできません。ユーザーはもはやあなた様のナレッジベースにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。',
|
||||
datasetUsedByApp: 'このナレッジベースは一部のアプリによって使用されています。アプリはこのナレッジベースを使用できなくなり、すべてのプロンプト設定とログは永久に削除されます。',
|
||||
datasetDeleted: 'ナレッジベースが削除されました',
|
||||
datasetDeleteFailed: 'ナレッジベースの削除に失敗しました',
|
||||
didYouKnow: 'ご存知ですか?',
|
||||
intro1: 'ナレッジはDifyアプリケーションに統合することができます',
|
||||
intro1: 'ナレッジベースはDifyアプリケーションに統合することができます',
|
||||
intro2: 'コンテキストとして',
|
||||
intro3: '、',
|
||||
intro4: 'または',
|
||||
@@ -85,7 +85,7 @@ const translation = {
|
||||
intro6: '単体のChatGPTインデックスプラグインとして公開するために',
|
||||
unavailable: '利用不可',
|
||||
unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります',
|
||||
datasets: 'ナレッジ',
|
||||
datasets: 'ナレッジベース',
|
||||
datasetsApi: 'API ACCESS',
|
||||
externalKnowledgeForm: {
|
||||
connect: '連携',
|
||||
@@ -141,8 +141,8 @@ const translation = {
|
||||
defaultRetrievalTip: 'デフォルトでは、マルチパス検索が使用されます。複数のナレッジベースから情報を取得した後、再ランキングを行います。',
|
||||
mixtureHighQualityAndEconomicTip: '高品質なナレッジベースとコスト重視のナレッジベースを混在させるには、Rerankモデルが必要です。',
|
||||
inconsistentEmbeddingModelTip: '選択されたナレッジベースの埋め込みモデルに一貫性がない場合、Rerankモデルが必要です。',
|
||||
mixtureInternalAndExternalTip: '内部ナレッジと外部ナレッジを混在させるには、Rerankモデルが必要です。',
|
||||
allExternalTip: '外部ナレッジのみを使用する場合、Rerankモデルを有効にするかを選択できます。有効にしない場合、検索結果はスコアに基づいてソートされます。異なるナレッジベースで検索戦略が一貫していないと、結果が不正確になる可能性があります。',
|
||||
mixtureInternalAndExternalTip: '内部と外部のナレッジベースを混在させる場合、Rerankモデルが必要です。',
|
||||
allExternalTip: '外部ナレッジベースのみを使用する場合、Rerankモデルを有効にするかを選択できます。有効にしない場合、検索結果はスコアに基づいてソートされます。異なるナレッジベースで検索戦略が一貫していないと、結果が不正確になる可能性があります。',
|
||||
retrievalSettings: '検索設定',
|
||||
rerankSettings: 'Rerank設定',
|
||||
weightedScore: {
|
||||
@@ -166,8 +166,56 @@ const translation = {
|
||||
cancel: 'キャンセル',
|
||||
},
|
||||
preprocessDocument: '{{num}}件のドキュメントを前処理',
|
||||
allKnowledge: 'すべての知識',
|
||||
allKnowledgeDescription: 'このワークスペースにすべてのナレッジを表示する場合に選択します。ワークスペースのオーナーのみがすべてのナレッジを管理できます。',
|
||||
allKnowledge: 'ナレッジベース全体',
|
||||
allKnowledgeDescription: 'このワークスペースにナレッジベース全体を表示する場合に選択します。ワークスペースのオーナーのみがすべてのナレッジベースを管理できます。',
|
||||
embeddingModelNotAvailable: 'Embeddingモデル不可用。',
|
||||
metadata: {
|
||||
metadata: 'メタデータ',
|
||||
addMetadata: 'メタデータを追加',
|
||||
chooseTime: '時間を選択',
|
||||
createMetadata: {
|
||||
title: '新規メタデータ',
|
||||
back: '戻る',
|
||||
type: 'タイプ',
|
||||
name: '名称',
|
||||
namePlaceholder: 'メタデータ名を入力',
|
||||
},
|
||||
checkName: {
|
||||
empty: 'メタデータ名を入力してください',
|
||||
invalid: 'メタデータ名は小文字、数字、アンダースコアのみを使用し、小文字で始める必要があります',
|
||||
},
|
||||
batchEditMetadata: {
|
||||
editMetadata: 'メタデータを編集',
|
||||
editDocumentsNum: '{{num}}件のドキュメントを編集',
|
||||
applyToAllSelectDocument: '選択したすべてのドキュメントに適用',
|
||||
applyToAllSelectDocumentTip: '上記の編集と新しいメタデータを選択したすべてのドキュメントに自動的に適用します。チェックしない場合、既にメタデータを持つドキュメントにのみ編集が適用されます。',
|
||||
multipleValue: '複数の値',
|
||||
},
|
||||
selectMetadata: {
|
||||
search: 'メタデータを検索',
|
||||
newAction: '新規メタデータ',
|
||||
manageAction: '管理',
|
||||
},
|
||||
datasetMetadata: {
|
||||
description: 'メタデータはドキュメントに関する情報で、ドキュメントの属性を説明するために使用されます。メタデータを活用することで、ドキュメントをより効率的に整理・管理できます。',
|
||||
addMetaData: 'メタデータを追加',
|
||||
values: '{{num}}個の値',
|
||||
disabled: '無効',
|
||||
rename: '名前変更',
|
||||
name: '名称',
|
||||
namePlaceholder: 'メタデータ名',
|
||||
builtIn: '組み込み',
|
||||
builtInDescription: '組み込みメタデータはシステムによって事前定義されたメタデータです。ここで組み込みメタデータの表示と管理ができます。',
|
||||
deleteTitle: '削除の確認',
|
||||
deleteContent: 'メタデータ「{{name}}」を削除してもよろしいですか?',
|
||||
},
|
||||
documentMetadata: {
|
||||
metadataToolTip: 'メタデータはドキュメントに関する情報で、ドキュメントの属性を説明するために使用されます。メタデータを活用することで、ドキュメントをより効率的に整理・管理できます。',
|
||||
startLabeling: 'ラベリングを開始',
|
||||
documentInformation: 'ドキュメント情報',
|
||||
technicalParameters: '技術パラメータ',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
||||
@@ -37,6 +37,7 @@ const translation = {
|
||||
HR: '人事',
|
||||
Workflow: 'ワークフロー',
|
||||
Agent: 'エージェント',
|
||||
Entertainment: 'エンターテイメント',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+17
-15
@@ -21,7 +21,7 @@ const translation = {
|
||||
marketplace: 'マーケットプレイスからインストール',
|
||||
},
|
||||
noInstalled: 'プラグインはインストールされていません',
|
||||
notFound: 'プラグインが見つかりませんでした',
|
||||
notFound: 'プラグインが見つかりません',
|
||||
},
|
||||
source: {
|
||||
github: 'GitHub',
|
||||
@@ -160,26 +160,28 @@ const translation = {
|
||||
upgrade: 'インストール',
|
||||
},
|
||||
error: {
|
||||
fetchReleasesError: 'リリースを取得できませんでした。後でもう一度お試しください。',
|
||||
fetchReleasesError: 'リリースを取得できません。後でもう一度お試しください。',
|
||||
inValidGitHubUrl: '無効なGitHub URLです。有効なURLを次の形式で入力してください: https://github.com/owner/repo',
|
||||
noReleasesFound: 'リリースは見つかりませんでした。GitHubリポジトリまたは入力URLを確認してください。',
|
||||
noReleasesFound: 'リリースは見つかりません。GitHubリポジトリまたは入力URLを確認してください。',
|
||||
},
|
||||
marketplace: {
|
||||
sortOption: {
|
||||
mostPopular: '最も人気のある',
|
||||
recentlyUpdated: '最近更新されました',
|
||||
newlyReleased: '新発売',
|
||||
firstReleased: '最初にリリースされた',
|
||||
},
|
||||
sortBy: '黒い街',
|
||||
empower: 'AI開発をサポートする',
|
||||
discover: '探索',
|
||||
and: 'と',
|
||||
pluginsResult: '{{num}} 件の結果',
|
||||
noPluginFound: 'プラグインが見つかりませんでした',
|
||||
moreFrom: 'マーケットプレイスからのさらなる情報',
|
||||
difyMarketplace: 'Difyマーケットプレイス',
|
||||
moreFrom: 'マーケットプレイスからのさらなる情報',
|
||||
noPluginFound: 'プラグインが見つかりません',
|
||||
pluginsResult: '{{num}} 件の結果',
|
||||
sortBy: '並べ替え',
|
||||
sortOption: {
|
||||
mostPopular: '人気順',
|
||||
recentlyUpdated: '最近更新順',
|
||||
newlyReleased: '新着順',
|
||||
firstReleased: 'リリース順',
|
||||
},
|
||||
viewMore: 'もっと見る',
|
||||
discover: '発見する',
|
||||
empower: 'AI開発を強化する',
|
||||
verifiedTip: 'このプラグインはDifyによって認証されています',
|
||||
partnerTip: 'このプラグインはDifyのパートナーによって認証されています',
|
||||
},
|
||||
task: {
|
||||
installError: '{{errorLength}} プラグインのインストールに失敗しました。表示するにはクリックしてください。',
|
||||
|
||||
+15
-15
@@ -1,31 +1,31 @@
|
||||
const translation = {
|
||||
input: '入力',
|
||||
result: '結果',
|
||||
detail: '詳細',
|
||||
tracing: 'トレース',
|
||||
detail: '詳細情報',
|
||||
tracing: '実行追跡',
|
||||
resultPanel: {
|
||||
status: 'ステータス',
|
||||
time: '経過時間',
|
||||
tokens: 'トークンの合計',
|
||||
time: '処理時間',
|
||||
tokens: 'トークン総数',
|
||||
},
|
||||
meta: {
|
||||
title: 'メタデータ',
|
||||
status: 'ステータス',
|
||||
status: '状態',
|
||||
version: 'バージョン',
|
||||
executor: '実行者',
|
||||
startTime: '開始時間',
|
||||
time: '経過時間',
|
||||
tokens: 'トークンの合計',
|
||||
steps: '実行ステップ',
|
||||
startTime: '開始時刻',
|
||||
time: '総処理時間',
|
||||
tokens: 'トークン総数',
|
||||
steps: '処理ステップ数',
|
||||
},
|
||||
resultEmpty: {
|
||||
title: 'この実行では JSON 形式のみが出力されます',
|
||||
tipLeft: 'にアクセスしてください',
|
||||
link: '詳細パネル',
|
||||
tipRight: '表示します。',
|
||||
title: '今回の実行ではJSON形式のみが出力されました',
|
||||
tipLeft: '詳細を確認するには',
|
||||
link: '詳細情報パネル',
|
||||
tipRight: 'へ移動してください',
|
||||
},
|
||||
circularInvocationTip: '現在のワークフローには、ツール/ノードの循環的な呼び出しがあります。',
|
||||
actionLogs: 'アクションログ',
|
||||
actionLogs: '操作ログ',
|
||||
circularInvocationTip: '現在のワークフローにツール/ノードの循環呼び出しが検出されました',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
||||
+48
-46
@@ -1,72 +1,74 @@
|
||||
const translation = {
|
||||
common: {
|
||||
welcome: '',
|
||||
appUnavailable: 'アプリが利用できません',
|
||||
appUnknownError: 'アプリが利用できません',
|
||||
appUnavailable: 'アプリケーションは利用できません',
|
||||
appUnknownError: 'アプリケーションは利用できません',
|
||||
},
|
||||
chat: {
|
||||
newChat: '新しいチャット',
|
||||
pinnedTitle: 'ピン留めされた',
|
||||
unpinnedTitle: 'チャット',
|
||||
newChatDefaultName: '新しい会話',
|
||||
resetChat: '会話をリセット',
|
||||
newChat: '新規チャット',
|
||||
newChatTip: '新規チャットが開始されています',
|
||||
chatSettingsTitle: 'チャット設定',
|
||||
chatFormTip: 'チャット開始後は設定を変更できません',
|
||||
pinnedTitle: 'ピン留め済み',
|
||||
unpinnedTitle: 'チャットリスト',
|
||||
newChatDefaultName: '新規チャット',
|
||||
resetChat: 'チャットをリセット',
|
||||
viewChatSettings: '設定を確認',
|
||||
poweredBy: 'Powered by',
|
||||
prompt: 'プロンプト',
|
||||
privatePromptConfigTitle: '会話の設定',
|
||||
publicPromptConfigTitle: '初期プロンプト',
|
||||
configStatusDes: '開始前に、会話の設定を変更できます',
|
||||
configDisabled:
|
||||
'前回のセッションの設定がこのセッションで使用されました。',
|
||||
privatePromptConfigTitle: '個別設定',
|
||||
publicPromptConfigTitle: '共通プロンプト設定',
|
||||
configStatusDes: '開始前に設定を変更できます',
|
||||
configDisabled: '前回の設定を適用中です',
|
||||
startChat: 'チャットを開始',
|
||||
privacyPolicyLeft:
|
||||
'アプリ開発者が提供する',
|
||||
privacyPolicyMiddle:
|
||||
'プライバシーポリシー',
|
||||
privacyPolicyRight:
|
||||
'をお読みください。',
|
||||
privacyPolicyLeft: '本アプリの',
|
||||
privacyPolicyMiddle: 'プライバシーポリシー',
|
||||
privacyPolicyRight: 'に同意の上ご利用ください',
|
||||
deleteConversation: {
|
||||
title: '会話を削除する',
|
||||
content: 'この会話を削除してもよろしいですか?',
|
||||
title: 'チャットの削除',
|
||||
content: 'このチャットを削除しますか?',
|
||||
},
|
||||
tryToSolve: '解決しようとしています',
|
||||
temporarySystemIssue: '申し訳ありません、一時的なシステムの問題が発生しました。',
|
||||
tryToSolve: '問題を解決する',
|
||||
temporarySystemIssue: 'システムに一時的な問題が発生しています',
|
||||
},
|
||||
generation: {
|
||||
tabs: {
|
||||
create: '一度だけ実行',
|
||||
create: '1回実行',
|
||||
batch: '一括実行',
|
||||
saved: '保存済み',
|
||||
},
|
||||
savedNoData: {
|
||||
title: 'まだ結果が保存されていません!',
|
||||
description: 'コンテンツの生成を開始し、保存された結果をこちらで見つけてください。',
|
||||
startCreateContent: 'コンテンツの作成を開始',
|
||||
title: '保存済みデータがありません',
|
||||
description: 'コンテンツ生成後に結果がここに表示されます',
|
||||
startCreateContent: '生成を開始',
|
||||
},
|
||||
title: 'AI Completion',
|
||||
queryTitle: 'コンテンツのクエリ',
|
||||
completionResult: 'Completion 結果',
|
||||
queryPlaceholder: 'クエリコンテンツを書いてください...',
|
||||
title: 'AI文章作成',
|
||||
queryTitle: '入力内容',
|
||||
completionResult: '生成結果',
|
||||
queryPlaceholder: '入力してください',
|
||||
run: '実行',
|
||||
execution: '処理中',
|
||||
executions: '{{num}}回実行',
|
||||
copy: 'コピー',
|
||||
resultTitle: 'AI Completion',
|
||||
noData: 'AIはここで必要なものを提供します。',
|
||||
csvUploadTitle: 'CSVファイルをここにドラッグアンドドロップするか、',
|
||||
browse: '参照',
|
||||
csvStructureTitle: 'CSVファイルは以下の構造に準拠する必要があります:',
|
||||
downloadTemplate: 'こちらからテンプレートをダウンロード',
|
||||
field: 'フィールド',
|
||||
resultTitle: 'AI生成結果',
|
||||
noData: 'AIがコンテンツを生成します',
|
||||
csvUploadTitle: 'CSVファイルをドロップするか',
|
||||
browse: 'ファイルを選択',
|
||||
csvStructureTitle: 'CSV形式要件:',
|
||||
downloadTemplate: 'テンプレートを取得',
|
||||
field: '',
|
||||
batchFailed: {
|
||||
info: '{{num}} 回の実行が失敗しました',
|
||||
retry: '再試行',
|
||||
outputPlaceholder: '出力コンテンツなし',
|
||||
info: '{{num}}件の失敗',
|
||||
retry: '再実行',
|
||||
outputPlaceholder: '出力なし',
|
||||
},
|
||||
errorMsg: {
|
||||
empty: 'アップロードされたファイルにコンテンツを入力してください。',
|
||||
fileStructNotMatch: 'アップロードされたCSVファイルが構造と一致しません。',
|
||||
emptyLine: '行 {{rowIndex}} が空です',
|
||||
invalidLine: '行 {{rowIndex}}: {{varName}} の値は空にできません',
|
||||
moreThanMaxLengthLine: '行 {{rowIndex}}: {{varName}} の値は {{maxLength}} 文字を超えることはできません',
|
||||
atLeastOne: 'アップロードされたファイルには少なくとも1行の入力が必要です。',
|
||||
empty: 'ファイル内容が空です',
|
||||
fileStructNotMatch: 'ファイル形式が不正です',
|
||||
emptyLine: '{{rowIndex}}行目: 内容が空です',
|
||||
invalidLine: '{{rowIndex}}行目: {{varName}}の入力が必要です',
|
||||
moreThanMaxLengthLine: '{{rowIndex}}行目: {{varName}}が制限長({{maxLength}})を超過',
|
||||
atLeastOne: '1行以上のデータが必要です',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const translation = {
|
||||
customToolTip: 'Difyカスタムツールの詳細',
|
||||
type: {
|
||||
all: 'すべて',
|
||||
builtIn: '組み込み',
|
||||
builtIn: 'ツール',
|
||||
custom: 'カスタム',
|
||||
workflow: 'ワークフロー',
|
||||
},
|
||||
@@ -13,7 +13,7 @@ const translation = {
|
||||
line2: 'Difyへのツールの貢献に興味があります。',
|
||||
viewGuide: 'ガイドを見る',
|
||||
},
|
||||
author: '著者',
|
||||
author: '著者:',
|
||||
auth: {
|
||||
unauthorized: '認証する',
|
||||
authorized: '認証済み',
|
||||
|
||||
+271
-185
@@ -8,6 +8,7 @@ const translation = {
|
||||
published: '公開済み',
|
||||
publish: '公開する',
|
||||
update: '更新',
|
||||
publishUpdate: '更新を公開',
|
||||
run: '実行',
|
||||
running: '実行中',
|
||||
inRunMode: '実行モード中',
|
||||
@@ -19,48 +20,50 @@ const translation = {
|
||||
goBackToEdit: '編集に戻る',
|
||||
conversationLog: '会話ログ',
|
||||
features: '機能',
|
||||
featuresDescription: 'Webアプリのユーザーエクスペリエンスを強化する',
|
||||
ImageUploadLegacyTip: '開始フォームでファイルタイプ変数を作成できるようになりました。まもなく、画像アップロード機能のサポートは終了いたします。',
|
||||
fileUploadTip: '画像アップロード機能がファイルのアップロード機能にアップグレードされました。',
|
||||
featuresDocLink: '詳細はこちら',
|
||||
featuresDescription: 'Webアプリの操作性を向上させる機能',
|
||||
ImageUploadLegacyTip: '開始フォームでファイル型変数が作成可能になりました。画像アップロード機能は今後サポート終了となります。',
|
||||
fileUploadTip: '画像アップロード機能がファイルアップロードに拡張されました',
|
||||
featuresDocLink: '詳細を見る',
|
||||
debugAndPreview: 'プレビュー',
|
||||
restart: '再起動',
|
||||
currentDraft: '現在の下書き',
|
||||
currentDraftUnpublished: '現在の下書き(未公開)',
|
||||
latestPublished: '最新の公開済み',
|
||||
latestPublished: '最新公開版',
|
||||
publishedAt: '公開日時',
|
||||
restore: '復元',
|
||||
versionHistory: 'バージョン履歴',
|
||||
exitVersions: 'バージョン履歴を閉じる',
|
||||
runApp: 'アプリを実行',
|
||||
batchRunApp: 'バッチでアプリを実行',
|
||||
openInExplore: '"探索" で開く',
|
||||
accessAPIReference: 'APIリファレンスにアクセス',
|
||||
batchRunApp: 'アプリを一括実行',
|
||||
openInExplore: '探索ページで開く',
|
||||
accessAPIReference: 'APIリファレンス',
|
||||
embedIntoSite: 'サイトに埋め込む',
|
||||
addTitle: 'タイトルを追加...',
|
||||
addDescription: '説明を追加...',
|
||||
noVar: '変数なし',
|
||||
noVar: '変数がありません',
|
||||
searchVar: '変数を検索',
|
||||
variableNamePlaceholder: '変数名',
|
||||
setVarValuePlaceholder: '変数を設定',
|
||||
needConnectTip: 'このステップは何にも接続されていません',
|
||||
maxTreeDepth: 'ブランチごとの最大制限は{{depth}}ノードです',
|
||||
variableNamePlaceholder: '変数名を入力',
|
||||
setVarValuePlaceholder: '変数値を設定',
|
||||
needConnectTip: '接続されていないステップがあります',
|
||||
maxTreeDepth: '1ブランチあたりの最大ノード数:{{depth}}',
|
||||
needEndNode: '終了ブロックを追加する必要があります',
|
||||
needAnswerNode: '回答ブロックを追加する必要があります',
|
||||
workflowProcess: 'ワークフロー処理',
|
||||
notRunning: 'まだ実行されていません',
|
||||
previewPlaceholder: 'チャットボットのデバッグを開始するには、以下のボックスにコンテンツを入力してください',
|
||||
previewPlaceholder: '入力欄にテキストを入力してチャットボットのデバッグを開始',
|
||||
effectVarConfirm: {
|
||||
title: '変数を削除',
|
||||
title: '変数の削除',
|
||||
content: '他のノードで変数が使用されています。それでも削除しますか?',
|
||||
},
|
||||
insertVarTip: 'クイック挿入のために\'/\'キーを押します',
|
||||
insertVarTip: '"/"キーで変数を挿入',
|
||||
processData: 'データ処理',
|
||||
input: '入力',
|
||||
output: '出力',
|
||||
jinjaEditorPlaceholder: '変数を挿入するには「/」または「{」を入力してください',
|
||||
viewOnly: '表示のみ',
|
||||
jinjaEditorPlaceholder: '「/」または 「{」で変数挿入',
|
||||
viewOnly: '閲覧のみ',
|
||||
showRunHistory: '実行履歴を表示',
|
||||
enableJinja: 'Jinjaテンプレートのサポートを有効にする',
|
||||
learnMore: '詳細はこちら',
|
||||
enableJinja: 'Jinjaテンプレートを有効化',
|
||||
learnMore: '詳細を見る',
|
||||
copy: 'コピー',
|
||||
duplicate: '複製',
|
||||
addBlock: 'ブロックを追加',
|
||||
@@ -68,44 +71,44 @@ const translation = {
|
||||
pointerMode: 'ポインターモード',
|
||||
handMode: 'ハンドモード',
|
||||
model: 'モデル',
|
||||
workflowAsTool: 'ツールとしてのワークフロー',
|
||||
workflowAsTool: 'ワークフローをツールどして公開する',
|
||||
configureRequired: '設定が必要',
|
||||
configure: '設定',
|
||||
manageInTools: 'ツールで管理',
|
||||
workflowAsToolTip: 'ワークフローの更新後、ツールの再設定が必要です。',
|
||||
manageInTools: 'ツールページで管理',
|
||||
workflowAsToolTip: 'ワークフロー更新後はツールの再設定が必要です',
|
||||
viewDetailInTracingPanel: '詳細を表示',
|
||||
syncingData: 'データを同期中,数秒程度で終わります。',
|
||||
syncingData: 'データ同期中。。。',
|
||||
importDSL: 'DSLをインポート',
|
||||
importDSLTip: '現在のドラフトは上書きされますので、インポートする際は、事前にワークフローをバックアップとしてエクスポートいただきますよう、お願い申し上げます。',
|
||||
backupCurrentDraft: '現在のドラフトをバックアップ',
|
||||
importDSLTip: '現在の下書きは上書きされます。インポート前にワークフローをエクスポートしてバックアップしてください',
|
||||
backupCurrentDraft: '現在の下書きをバックアップ',
|
||||
chooseDSL: 'DSL(yml)ファイルを選択',
|
||||
overwriteAndImport: 'オーバライトとインポート',
|
||||
overwriteAndImport: '上書きしてインポート',
|
||||
importFailure: 'インポート失敗',
|
||||
importWarning: '注意事項',
|
||||
importWarningDetails: 'DSLバージョンの違いにより機能に影響が出る可能性があります',
|
||||
importSuccess: 'インポート成功',
|
||||
parallelRun: '並列実行',
|
||||
parallelTip: {
|
||||
click: {
|
||||
title: 'クリック',
|
||||
desc: '追加する',
|
||||
desc: 'で追加',
|
||||
},
|
||||
drag: {
|
||||
title: 'ドラッグ',
|
||||
desc: '接続するには',
|
||||
desc: 'で接続',
|
||||
},
|
||||
limit: '並列処理は {{num}} ブランチに制限されています。',
|
||||
depthLimit: '{{num}}レイヤーの平行ネストレイヤーの制限',
|
||||
limit: '並列処理可能ブランチ数:{{num}}',
|
||||
depthLimit: '並列ネスト最大階層数:{{num}}',
|
||||
},
|
||||
parallelRun: 'パラレルラン',
|
||||
disconnect: '切る',
|
||||
jumpToNode: 'このノードにジャンプします',
|
||||
disconnect: '接続解除',
|
||||
jumpToNode: 'このノードに移動',
|
||||
addParallelNode: '並列ノードを追加',
|
||||
parallel: '並列',
|
||||
branch: 'ブランチ',
|
||||
importWarning: '注意',
|
||||
importWarningDetails: 'DSL のバージョンの違いが特定の機能に影響を与える場合があります',
|
||||
onFailure: '失敗時',
|
||||
addFailureBranch: '失敗ブランチを追加',
|
||||
noHistory: '履歴なし',
|
||||
loadMore: 'より多くのワークフローをロードする',
|
||||
loadMore: 'さらに読み込む',
|
||||
noHistory: '履歴がありません',
|
||||
},
|
||||
env: {
|
||||
envPanelTitle: '環境変数',
|
||||
@@ -115,36 +118,36 @@ const translation = {
|
||||
title: '環境変数を追加',
|
||||
editTitle: '環境変数を編集',
|
||||
type: 'タイプ',
|
||||
name: '名前',
|
||||
namePlaceholder: '変数名',
|
||||
name: '変数名',
|
||||
namePlaceholder: '変数名を入力',
|
||||
value: '値',
|
||||
valuePlaceholder: '変数値',
|
||||
secretTip: 'このような機密情報やデータは、定義に使用され、DSLの設定は情報漏洩を防ぐために特別に構成されています。',
|
||||
valuePlaceholder: '変数値を入力',
|
||||
secretTip: 'この変数は機密情報やデータを定義するために使用されます。DSL をエクスポートするときに漏洩防止メカニズムを設定されます。',
|
||||
},
|
||||
export: {
|
||||
title: 'シークレット環境変数をエクスポートしますか?',
|
||||
checkbox: 'シークレット値をエクスポート',
|
||||
checkbox: 'シークレット値を含む',
|
||||
ignore: 'DSLをエクスポート',
|
||||
export: 'シークレット値を含むDSLをエクスポート',
|
||||
export: 'シークレット値付きでエクスポート',
|
||||
},
|
||||
},
|
||||
chatVariable: {
|
||||
panelTitle: '会話変数',
|
||||
panelDescription: '会話変数は、LLMが記憶すべき対話情報を保存するために使用されます。この情報には、対話の履歴、アップロードされたファイル、ユーザーの好みなどが含まれます。読み書きが可能です。',
|
||||
docLink: '詳しくはドキュメントをご覧ください。',
|
||||
panelDescription: '対話情報を保存・管理(会話履歴/ファイル/ユーザー設定など)。書き換えができます。',
|
||||
docLink: '詳細ドキュメント',
|
||||
button: '変数を追加',
|
||||
modal: {
|
||||
title: '会話変数を追加',
|
||||
editTitle: '会話変数を編集',
|
||||
name: '名前',
|
||||
namePlaceholder: '変数名前',
|
||||
name: '変数名',
|
||||
namePlaceholder: '変数名を入力',
|
||||
type: 'タイプ',
|
||||
value: 'デフォルト値',
|
||||
valuePlaceholder: 'デフォルト値、設定しない場合は空白にしでください',
|
||||
description: '説明',
|
||||
descriptionPlaceholder: '変数の説明',
|
||||
editInJSON: 'JSONで編集する',
|
||||
oneByOne: '次々に追加する',
|
||||
descriptionPlaceholder: '変数の説明を入力',
|
||||
editInJSON: 'JSONで編集',
|
||||
oneByOne: '個別追加',
|
||||
editInForm: 'フォームで編集',
|
||||
arrayValue: '値',
|
||||
addArrayValue: '値を追加',
|
||||
@@ -152,19 +155,19 @@ const translation = {
|
||||
objectType: 'タイプ',
|
||||
objectValue: 'デフォルト値',
|
||||
},
|
||||
storedContent: '保存されたコンテンツ',
|
||||
updatedAt: '更新日は',
|
||||
storedContent: '保存内容',
|
||||
updatedAt: '最終更新:',
|
||||
},
|
||||
changeHistory: {
|
||||
title: '変更履歴',
|
||||
placeholder: 'まだ何も変更していません',
|
||||
placeholder: 'まだ何も変更されていません',
|
||||
clearHistory: '履歴をクリア',
|
||||
hint: 'ヒント',
|
||||
hintText: '編集アクションは変更履歴に記録され、このセッションの間にデバイスに保存されます。エディターを終了すると、この履歴は消去されます。',
|
||||
stepBackward_one: '{{count}} ステップ後退',
|
||||
stepBackward_other: '{{count}} ステップ後退',
|
||||
stepForward_one: '{{count}} ステップ前進',
|
||||
stepForward_other: '{{count}} ステップ前進',
|
||||
hintText: 'エディターでの編集操作は、エディターを離れるまで、お使いのデバイスに記録されます。この履歴は、エディターを離れると消去されます。',
|
||||
stepBackward_one: '{{count}} ステップ戻る',
|
||||
stepBackward_other: '{{count}} ステップ戻る',
|
||||
stepForward_one: '{{count}} ステップ進む',
|
||||
stepForward_other: '{{count}} ステップ進む',
|
||||
sessionStart: 'セッション開始',
|
||||
currentState: '現在の状態',
|
||||
nodeTitleChange: 'ブロックのタイトルが変更されました',
|
||||
@@ -175,16 +178,17 @@ const translation = {
|
||||
nodePaste: 'ブロックが貼り付けられました',
|
||||
nodeDelete: 'ブロックが削除されました',
|
||||
nodeAdd: 'ブロックが追加されました',
|
||||
nodeResize: 'ブロックがリサイズされました',
|
||||
noteAdd: 'ノートが追加されました',
|
||||
noteChange: 'ノートが変更されました',
|
||||
noteDelete: 'ノートが削除されました',
|
||||
edgeDelete: 'ブロックが切断されました',
|
||||
nodeResize: 'ブロックのサイズが変更されました',
|
||||
noteAdd: '注釈が追加されました',
|
||||
noteChange: '注釈が変更されました',
|
||||
noteDelete: '注釈が削除されました',
|
||||
edgeDelete: 'ブロックの接続が解除されました',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}}は必須です',
|
||||
fieldRequired: '{{field}} は必須です',
|
||||
rerankModelRequired: 'Rerank モデルが設定されていません',
|
||||
authRequired: '認証が必要です',
|
||||
invalidJson: '{{field}}は無効です',
|
||||
invalidJson: '{{field}} は無効な JSON です',
|
||||
fields: {
|
||||
variable: '変数名',
|
||||
variableValue: '変数値',
|
||||
@@ -193,33 +197,33 @@ const translation = {
|
||||
rerankModel: 'Rerankモデル',
|
||||
visionVariable: 'ビジョン変数',
|
||||
},
|
||||
invalidVariable: '無効な変数',
|
||||
rerankModelRequired: 'モデルの再ランク付けをオンにする前に、モデルが設定で正常に構成されていることを確認してください。',
|
||||
invalidVariable: '無効な変数です',
|
||||
noValidTool: '{{field}} に利用可能なツールがありません',
|
||||
toolParameterRequired: '{{field}}: パラメータ [{{param}}] は必須です',
|
||||
noValidTool: '{{field}} 有効なツールが選択されていません',
|
||||
},
|
||||
singleRun: {
|
||||
testRun: 'テスト実行',
|
||||
startRun: '実行を開始',
|
||||
startRun: '実行開始',
|
||||
running: '実行中',
|
||||
testRunIteration: 'テスト実行イテレーション',
|
||||
testRunIteration: 'テスト実行(イテレーション)',
|
||||
testRunLoop: 'テスト実行(ループ)',
|
||||
back: '戻る',
|
||||
iteration: 'イテレーション',
|
||||
loop: 'ループ',
|
||||
},
|
||||
tabs: {
|
||||
'searchBlock': 'ブロックを検索',
|
||||
'searchBlock': 'ブロック検索',
|
||||
'blocks': 'ブロック',
|
||||
'searchTool': '検索ツール',
|
||||
'searchTool': 'ツール検索',
|
||||
'tools': 'ツール',
|
||||
'allTool': 'すべて',
|
||||
'workflowTool': 'ワークフロー',
|
||||
'builtInTool': '組み込み',
|
||||
'customTool': 'カスタム',
|
||||
'question-understand': '質問の理解',
|
||||
'workflowTool': 'ワークフロー',
|
||||
'question-understand': '問題理解',
|
||||
'logic': 'ロジック',
|
||||
'transform': '変換',
|
||||
'utilities': 'ユーティリティ',
|
||||
'noResult': '一致するものが見つかりませんでした',
|
||||
'utilities': 'ツール',
|
||||
'noResult': '該当なし',
|
||||
'plugin': 'プラグイン',
|
||||
'agent': 'エージェント戦略',
|
||||
},
|
||||
@@ -228,10 +232,10 @@ const translation = {
|
||||
'end': '終了',
|
||||
'answer': '回答',
|
||||
'llm': 'LLM',
|
||||
'knowledge-retrieval': '知識取得',
|
||||
'knowledge-retrieval': '知識検索',
|
||||
'question-classifier': '質問分類器',
|
||||
'if-else': 'IF/ELSE',
|
||||
'code': 'コード',
|
||||
'code': 'コード実行',
|
||||
'template-transform': 'テンプレート',
|
||||
'http-request': 'HTTPリクエスト',
|
||||
'variable-assigner': '変数代入器',
|
||||
@@ -239,54 +243,64 @@ const translation = {
|
||||
'assigner': '変数代入',
|
||||
'iteration-start': 'イテレーション開始',
|
||||
'iteration': 'イテレーション',
|
||||
'parameter-extractor': 'パラメーター抽出',
|
||||
'document-extractor': 'テキスト抽出ツール',
|
||||
'parameter-extractor': 'パラメータ抽出',
|
||||
'document-extractor': 'テキスト抽出',
|
||||
'list-operator': 'リスト処理',
|
||||
'agent': 'エージェント',
|
||||
'loop-start': 'ループ開始',
|
||||
'loop': 'ループ',
|
||||
},
|
||||
blocksAbout: {
|
||||
'start': 'ワークフローの開始に必要なパラメータを定義します',
|
||||
'end': 'ワークフローの終了と結果のタイプを定義します',
|
||||
'answer': 'チャット会話の応答内容を定義します',
|
||||
'llm': '大規模言語モデルを呼び出して質問に回答したり、自然言語を処理したりします',
|
||||
'knowledge-retrieval': 'ユーザーの質問に関連するテキストコンテンツを知識からクエリできるようにします',
|
||||
'question-classifier': 'ユーザーの質問の分類条件を定義し、LLMは分類記述に基づいて会話がどのように進行するかを定義できます',
|
||||
'if-else': 'IF/ELSE条件に基づいてワークフローを2つのブランチに分割できます',
|
||||
'code': 'カスタムロジックを実装するためにPythonまたはNodeJSコードを実行します',
|
||||
'template-transform': 'Jinjaテンプレート構文を使用してデータを文字列に変換します',
|
||||
'http-request': 'HTTPプロトコル経由でサーバーリクエストを送信できます',
|
||||
'variable-assigner': '複数のブランチの変数を1つの変数に集約し、下流のノードに対して統一された設定を行います。',
|
||||
'assigner': '変数代入ノードは、書き込み可能な変数(例えば、会話変数)に値を割り当てるために使用されます。',
|
||||
'variable-aggregator': '複数のブランチの変数を1つの変数に集約し、下流のノードに対して統一された設定を行います。',
|
||||
'iteration': 'リストオブジェクトに対して複数のステップを実行し、すべての結果が出力されるまで繰り返します。',
|
||||
'parameter-extractor': '自然言語からツールの呼び出しやHTTPリクエストのための構造化されたパラメーターを抽出するためにLLMを使用します。',
|
||||
'document-extractor': 'アップロードされたドキュメントを LLM で簡単に理解できるテキストのコンテンツに解析するために使用されます。',
|
||||
'list-operator': '配列のコンテンツをフィルタリングまたはソートするために使用されます。',
|
||||
'agent': '大規模言語モデルを呼び出して質問に答えたり自然言語を処理したりする',
|
||||
'start': 'ワークフロー開始時の初期パラメータを定義します。',
|
||||
'end': 'ワークフローの終了条件と結果のタイプを定義します。',
|
||||
'answer': 'チャットダイアログの返答内容を定義します。',
|
||||
'llm': '大規模言語モデルを呼び出して質問回答や自然言語処理を実行します。',
|
||||
'knowledge-retrieval': 'ナレッジベースからユーザー質問に関連するテキストを検索します。',
|
||||
'question-classifier': '質問の分類条件を定義し、LLMが分類に基づいて対話フローを制御します。',
|
||||
'if-else': 'if/else条件でワークフローを2つの分岐に分割します。',
|
||||
'code': 'Python/NodeJSコードを実行してカスタムロジックを実装します。',
|
||||
'template-transform': 'Jinjaテンプレート構文でデータを文字列に変換します。',
|
||||
'http-request': 'HTTPリクエストを送信できます。',
|
||||
'variable-assigner': '複数分岐の変数を集約し、下流ノードの設定を統一します。',
|
||||
'assigner': '書き込み可能な変数(例:会話変数)への値の割り当てを行います。',
|
||||
'variable-aggregator': '複数分岐の変数を集約し、下流ノードの設定を統一します。',
|
||||
'iteration': 'リスト要素に対して反復処理を実行し全結果を出力します。',
|
||||
'loop': '終了条件達成まで、または最大反復回数までロジックを繰り返します。',
|
||||
'parameter-extractor': '自然言語から構造化パラメータを抽出し、後続処理で利用します。',
|
||||
'document-extractor': 'アップロード文書をLLM処理用に最適化されたテキストに変換します。',
|
||||
'list-operator': '配列のフィルタリングやソート処理を行います。',
|
||||
'agent': '大規模言語モデルを活用した質問応答や自然言語処理を実行します。',
|
||||
},
|
||||
operator: {
|
||||
zoomIn: '拡大',
|
||||
zoomOut: '縮小',
|
||||
zoomTo50: '50%にズーム',
|
||||
zoomTo100: '100%にズーム',
|
||||
zoomToFit: 'フィットにズーム',
|
||||
zoomTo50: '50%サイズ',
|
||||
zoomTo100: '等倍表示',
|
||||
zoomToFit: '画面に合わせる',
|
||||
},
|
||||
variableReference: {
|
||||
noAvailableVars: '利用可能な変数がありません',
|
||||
noVarsForOperation: 'この操作に割り当て可能な変数が存在しません。',
|
||||
noAssignedVars: '割り当て可能な変数がありません',
|
||||
assignedVarsDescription: '書き込み可能な変数(例:',
|
||||
conversationVars: '会話変数',
|
||||
},
|
||||
panel: {
|
||||
userInputField: 'ユーザー入力フィールド',
|
||||
changeBlock: 'ブロックを変更',
|
||||
userInputField: 'ユーザー入力欄',
|
||||
changeBlock: 'ノード変更',
|
||||
helpLink: 'ヘルプリンク',
|
||||
about: '情報',
|
||||
createdBy: '作成者 ',
|
||||
about: '詳細',
|
||||
createdBy: '作成者',
|
||||
nextStep: '次のステップ',
|
||||
addNextStep: 'このワークフローで次のブロックを追加',
|
||||
selectNextStep: '次のブロックを選択',
|
||||
runThisStep: 'このステップを実行',
|
||||
addNextStep: 'このワークフローで次ノードを追加',
|
||||
selectNextStep: '次ノード選択',
|
||||
runThisStep: 'このステップ実行',
|
||||
checklist: 'チェックリスト',
|
||||
checklistTip: '公開する前にすべての問題が解決されていることを確認してください',
|
||||
checklistResolved: 'すべての問題が解決されました',
|
||||
organizeBlocks: 'ブロックを整理',
|
||||
checklistTip: '公開前に全ての項目を確認してください',
|
||||
checklistResolved: '全てのチェックが完了しました',
|
||||
organizeBlocks: 'ノード整理',
|
||||
change: '変更',
|
||||
optional: '(オプション)',
|
||||
optional: '(任意)',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
@@ -295,54 +309,54 @@ const translation = {
|
||||
memory: {
|
||||
memory: 'メモリ',
|
||||
memoryTip: 'チャットメモリ設定',
|
||||
windowSize: 'ウィンドウサイズ',
|
||||
windowSize: 'メモリウィンドウサイズ',
|
||||
conversationRoleName: '会話ロール名',
|
||||
user: 'ユーザー接頭辞',
|
||||
assistant: 'アシスタント接頭辞',
|
||||
},
|
||||
memories: {
|
||||
title: 'メモリ',
|
||||
tip: 'チャットメモリ',
|
||||
tip: 'チャットの記憶管理',
|
||||
builtIn: '組み込み',
|
||||
},
|
||||
errorHandle: {
|
||||
title: '例外処理',
|
||||
tip: 'ノード例外発生時の処理ポリシーを設定',
|
||||
none: {
|
||||
title: '処理なし',
|
||||
desc: '例外が発生して処理されない場合、ノードは実行を停止します',
|
||||
desc: '例外発生時に処理を停止',
|
||||
},
|
||||
defaultValue: {
|
||||
title: 'デフォルト値',
|
||||
desc: '例外が発生した場合は、デフォルトの出力コンテンツを指定します。',
|
||||
tip: '例外が発生した場合は、以下の値を返します。',
|
||||
inLog: 'ノード例外、デフォルト値に従って出力します。',
|
||||
output: '出力デフォルト値',
|
||||
desc: '例外発生時のデフォルト出力',
|
||||
tip: '例外発生時に返される値:',
|
||||
inLog: 'ノード例外 - デフォルト値を出力',
|
||||
output: 'デフォルト値出力',
|
||||
},
|
||||
failBranch: {
|
||||
title: 'エラーブランチ',
|
||||
customize: 'キャンバスに移動して、エラーブランチのロジックをカスタマイズします。',
|
||||
inLog: '例外が発生した場合は、エラーしたブランチを自動的に実行します。ノード出力は、エラータイプとエラーメッセージを返し、それらをダウンストリームに渡します。',
|
||||
desc: '例外が発生した場合は、エラーブランチを実行します',
|
||||
customizeTip: 'エラーブランチがアクティブになっても、ノードによってスローされた例外はプロセスを終了させません。代わりに、事前定義された エラーブランチが自動的に実行されるため、エラーメッセージ、レポート、修正アクション、またはスキップアクションを柔軟に提供できます。',
|
||||
title: '例外分岐',
|
||||
desc: '例外発生時に分岐を実行',
|
||||
customize: '失敗分岐ロジックをカスタマイズ',
|
||||
customizeTip: '例外発生時、失敗分岐でエラー処理を柔軟に設定可能(エラーログ表示/修復処理/操作スキップ等)',
|
||||
inLog: 'ノード例外 - 失敗分岐を実行。エラー情報を下流に伝播',
|
||||
},
|
||||
partialSucceeded: {
|
||||
tip: 'プロセスに{{num}}ノードが異常に動作していますので、トレースに移動してログを確認してください。',
|
||||
tip: '{{num}}個のノードで異常発生。ログはトレース画面で確認可能',
|
||||
},
|
||||
title: 'エラー処理',
|
||||
tip: 'ノードが例外を検出したときにトリガーされる例外処理戦略。',
|
||||
},
|
||||
retry: {
|
||||
retry: '再試行',
|
||||
retryOnFailure: '失敗時の再試行',
|
||||
maxRetries: '最大再試行回数',
|
||||
retryOnFailure: '失敗時再試行',
|
||||
maxRetries: '最大試行回数',
|
||||
retryInterval: '再試行間隔',
|
||||
retrying: '再試行。。。',
|
||||
retryFailed: '再試行に失敗しました',
|
||||
retryTimes: '失敗時 {{times}}回再試行',
|
||||
retrying: '再試行中...',
|
||||
retrySuccessful: '再試行成功',
|
||||
retryFailed: '再試行失敗',
|
||||
retryFailedTimes: '{{times}}回再試行失敗',
|
||||
times: '回',
|
||||
ms: 'ms',
|
||||
retryTimes: '失敗時に{{times}}回再試行',
|
||||
retrySuccessful: '再試行に成功しました',
|
||||
retries: '{{num}} 回の再試行',
|
||||
retryFailedTimes: '{{times}}回の再試行が失敗しました',
|
||||
ms: 'ミリ秒',
|
||||
retries: '再試行回数: {{num}}',
|
||||
},
|
||||
},
|
||||
start: {
|
||||
@@ -353,17 +367,17 @@ const translation = {
|
||||
query: 'ユーザー入力',
|
||||
memories: {
|
||||
des: '会話履歴',
|
||||
type: 'メッセージタイプ',
|
||||
type: 'メッセージ種別',
|
||||
content: 'メッセージ内容',
|
||||
},
|
||||
files: 'ファイルリスト',
|
||||
files: 'ファイル一覧',
|
||||
},
|
||||
noVarTip: 'ワークフローで使用できる入力を設定します',
|
||||
noVarTip: '入力設定はワークフロー内で利用可能',
|
||||
},
|
||||
end: {
|
||||
outputs: '出力',
|
||||
outputs: '出力設定',
|
||||
output: {
|
||||
type: '出力タイプ',
|
||||
type: '出力形式',
|
||||
variable: '出力変数',
|
||||
},
|
||||
type: {
|
||||
@@ -373,75 +387,106 @@ const translation = {
|
||||
},
|
||||
},
|
||||
answer: {
|
||||
answer: '回答',
|
||||
answer: '応答',
|
||||
outputVars: '出力変数',
|
||||
},
|
||||
llm: {
|
||||
model: 'モデル',
|
||||
model: 'AIモデル',
|
||||
variables: '変数',
|
||||
context: 'コンテキスト',
|
||||
contextTooltip: 'コンテキストとして知識をインポートできます',
|
||||
notSetContextInPromptTip: 'コンテキスト機能を有効にするには、PROMPTにコンテキスト変数を記入してください。',
|
||||
contextTooltip: 'ナレッジベースをコンテキストとして利用',
|
||||
notSetContextInPromptTip: 'コンテキスト利用時はプロンプトに変数を明記してください',
|
||||
prompt: 'プロンプト',
|
||||
addMessage: 'メッセージ追加',
|
||||
roleDescription: {
|
||||
system: '会話の高レベルな命令を与えます',
|
||||
user: 'モデルへの指示、クエリ、またはテキストベースの入力を提供します',
|
||||
assistant: 'ユーザーメッセージに基づいてモデルの応答',
|
||||
system: '対話の基本動作を定義',
|
||||
user: '指示/質問を入力',
|
||||
assistant: 'ユーザー入力への応答',
|
||||
},
|
||||
addMessage: 'メッセージを追加',
|
||||
vision: 'ビジョン',
|
||||
files: 'ファイル',
|
||||
resolution: {
|
||||
name: '解像度',
|
||||
high: '高い',
|
||||
low: '低い',
|
||||
high: '高',
|
||||
low: '低',
|
||||
},
|
||||
outputVars: {
|
||||
output: 'コンテンツを生成',
|
||||
usage: 'モデルの使用情報',
|
||||
output: '生成内容',
|
||||
usage: 'モデル使用量',
|
||||
},
|
||||
singleRun: {
|
||||
variable: '変数',
|
||||
},
|
||||
sysQueryInUser: 'ユーザーメッセージにsys.queryが必要です',
|
||||
sysQueryInUser: 'ユーザーメッセージにsys.queryを含めてください',
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
queryVariable: 'クエリ変数',
|
||||
knowledge: 'ナレッジ',
|
||||
queryVariable: '検索変数',
|
||||
knowledge: 'ナレッジベース',
|
||||
outputVars: {
|
||||
output: '検索されたセグメント化されたデータ',
|
||||
content: 'セグメント化されたコンテンツ',
|
||||
title: 'セグメント化されたタイトル',
|
||||
icon: 'セグメント化されたアイコン',
|
||||
url: 'セグメント化されたURL',
|
||||
metadata: 'その他のメタデータ',
|
||||
output: '検索結果セグメント',
|
||||
content: 'セグメント内容',
|
||||
title: 'セグメントタイトル',
|
||||
icon: 'セグメントアイコン',
|
||||
url: 'セグメントURL',
|
||||
metadata: 'メタデータ',
|
||||
},
|
||||
metadata: {
|
||||
title: 'メタデータフィルタ',
|
||||
tip: 'タグ/カテゴリ等の属性で検索を絞り込み',
|
||||
options: {
|
||||
disabled: {
|
||||
title: '無効',
|
||||
subTitle: 'フィルタリング不使用',
|
||||
},
|
||||
automatic: {
|
||||
title: '自動生成',
|
||||
subTitle: '検索履歴からフィルタ条件を自動生成',
|
||||
desc: 'Query Variable(検索変数)に基づきフィルタ条件を自動生成',
|
||||
},
|
||||
manual: {
|
||||
title: '手動設定',
|
||||
subTitle: 'メタデータの条件を手動で追加',
|
||||
},
|
||||
},
|
||||
panel: {
|
||||
title: 'メタデータのフィルタ条件',
|
||||
conditions: '条件一覧',
|
||||
add: '条件追加',
|
||||
search: 'メタデータ検索',
|
||||
placeholder: '値を入力',
|
||||
datePlaceholder: '日付選択...',
|
||||
select: '変数選択...',
|
||||
},
|
||||
},
|
||||
},
|
||||
http: {
|
||||
inputVars: '入力変数',
|
||||
api: 'API',
|
||||
apiPlaceholder: 'URLを入力、「/」を入力して変数を挿入',
|
||||
notStartWithHttp: 'APIはhttp://またはhttps://で始まる必要があります',
|
||||
apiPlaceholder: 'URLを入力(変数使用時は"/"を入力)',
|
||||
extractListPlaceholder: 'リスト番号を入力(変数使用時は"/"を入力)',
|
||||
notStartWithHttp: 'APIは http:// または https:// で始まってください',
|
||||
key: 'キー',
|
||||
type: 'タイプ',
|
||||
value: '値',
|
||||
bulkEdit: '一括編集',
|
||||
keyValueEdit: 'キー-値の編集',
|
||||
keyValueEdit: 'キーバリュー編集',
|
||||
headers: 'ヘッダー',
|
||||
params: 'パラメータ',
|
||||
body: 'ボディ',
|
||||
binaryFileVariable: 'バイナリファイル変数',
|
||||
outputVars: {
|
||||
body: 'レスポンスコンテンツ',
|
||||
statusCode: 'レスポンスステータスコード',
|
||||
headers: 'レスポンスヘッダーリストJSON',
|
||||
files: 'ファイルリスト',
|
||||
headers: 'レスポンスヘッダ(JSON)',
|
||||
files: 'ファイル一覧',
|
||||
},
|
||||
authorization: {
|
||||
'authorization': '認証',
|
||||
'authorizationType': '認証タイプ',
|
||||
'no-auth': 'なし',
|
||||
'api-key': 'APIキー',
|
||||
'auth-type': '認証タイプ',
|
||||
'basic': '基本',
|
||||
'auth-type': 'API認証タイプ',
|
||||
'basic': 'ベーシック',
|
||||
'bearer': 'Bearer',
|
||||
'custom': 'カスタム',
|
||||
'api-key-title': 'APIキー',
|
||||
@@ -449,19 +494,16 @@ const translation = {
|
||||
},
|
||||
insertVarPlaceholder: '変数を挿入するには\'/\'を入力してください',
|
||||
timeout: {
|
||||
title: 'タイムアウト',
|
||||
title: 'タイムアウト設定',
|
||||
connectLabel: '接続タイムアウト',
|
||||
connectPlaceholder: '接続タイムアウトを秒で入力',
|
||||
connectPlaceholder: '接続タイムアウト(秒)',
|
||||
readLabel: '読み取りタイムアウト',
|
||||
readPlaceholder: '読み取りタイムアウトを秒で入力',
|
||||
readPlaceholder: '読み取りタイムアウト(秒)',
|
||||
writeLabel: '書き込みタイムアウト',
|
||||
writePlaceholder: '書き込みタイムアウトを秒で入力',
|
||||
writePlaceholder: '書き込みタイムアウト(秒)',
|
||||
},
|
||||
type: 'タイプ',
|
||||
binaryFileVariable: 'バイナリファイル変数',
|
||||
extractListPlaceholder: 'リスト項目のインデックスを入力し、変数を挿入 \'/\' と入力します',
|
||||
curl: {
|
||||
title: 'cURLからのインポート',
|
||||
title: 'cURLからインポート',
|
||||
placeholder: 'ここにcURL文字列を貼り付けます',
|
||||
},
|
||||
},
|
||||
@@ -653,6 +695,25 @@ const translation = {
|
||||
MaxParallelismDesc: '最大並列処理は、1 回の反復で同時に実行されるタスクの数を制御するために使用されます。',
|
||||
answerNodeWarningDesc: '並列モードの警告: 応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。',
|
||||
},
|
||||
loop: {
|
||||
deleteTitle: 'ループノードを削除しますか?',
|
||||
deleteDesc: 'ループノードを削除すると、全ての子ノードが削除されます。',
|
||||
input: '入力',
|
||||
output: '出力変数',
|
||||
loop_one: '{{count}}回',
|
||||
loop_other: '{{count}}回',
|
||||
currentLoop: '現在のループ',
|
||||
breakCondition: 'ループ終了条件',
|
||||
breakConditionTip: 'ループ内の変数やセッション変数を参照し、終了条件を設定できます。',
|
||||
loopMaxCount: '最大ループ回数',
|
||||
loopMaxCountError: '最大ループ回数は1から{{maxCount}}の範囲で正しく入力してください。',
|
||||
errorResponseMethod: 'エラー対応方法',
|
||||
ErrorMethod: {
|
||||
operationTerminated: 'エラー時に処理を終了',
|
||||
continueOnError: 'エラーを無視して継続',
|
||||
removeAbnormalOutput: '異常出力を除外',
|
||||
},
|
||||
},
|
||||
note: {
|
||||
addNote: 'コメントを追加',
|
||||
editor: {
|
||||
@@ -771,12 +832,37 @@ const translation = {
|
||||
tracing: {
|
||||
stopBy: '{{user}}によって停止',
|
||||
},
|
||||
variableReference: {
|
||||
noVarsForOperation: '選択した操作で代入できる変数はありません。',
|
||||
noAvailableVars: '使用可能な変数がありません',
|
||||
noAssignedVars: '使用可能な代入変数がありません',
|
||||
assignedVarsDescription: '代入変数は、次のような書き込み可能な変数である必要があります。',
|
||||
conversationVars: '会話変数',
|
||||
versionHistory: {
|
||||
title: 'バージョン',
|
||||
currentDraft: '現在の下書き',
|
||||
latest: '最新版',
|
||||
filter: {
|
||||
all: 'すべて',
|
||||
onlyYours: '自分のみ',
|
||||
onlyShowNamedVersions: '名前付きバージョンのみ',
|
||||
reset: 'リセット',
|
||||
empty: '該当するバージョンがありません',
|
||||
},
|
||||
defaultName: '名称未設定',
|
||||
nameThisVersion: 'バージョン名を付ける',
|
||||
editVersionInfo: 'バージョン情報を編集',
|
||||
editField: {
|
||||
title: 'タイトル',
|
||||
releaseNotes: 'リリースノート',
|
||||
titleLengthLimit: 'タイトルは{{limit}}文字以内で入力してください',
|
||||
releaseNotesLengthLimit: 'リリースノートは{{limit}}文字以内で入力してください',
|
||||
},
|
||||
releaseNotesPlaceholder: '変更内容を入力してください',
|
||||
restorationTip: 'バージョンを復元すると、現在の下書きが上書きされます',
|
||||
deletionTip: '削除したデータは復元できません。よろしいですか?',
|
||||
action: {
|
||||
restoreSuccess: '復元が完了しました',
|
||||
restoreFailure: '復元に失敗しました',
|
||||
deleteSuccess: '削除が完了しました',
|
||||
deleteFailure: '削除に失敗しました',
|
||||
updateSuccess: '更新が完了しました',
|
||||
updateFailure: '更新に失敗しました',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app'
|
||||
import type {
|
||||
RerankingModeEnum,
|
||||
WeightedScoreEnum,
|
||||
} from '@/models/datasets'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import type {
|
||||
@@ -165,6 +166,7 @@ export type DatasetConfigs = {
|
||||
}
|
||||
reranking_mode?: RerankingModeEnum
|
||||
weights?: {
|
||||
weight_type: WeightedScoreEnum
|
||||
vector_setting: {
|
||||
vector_weight: number
|
||||
embedding_provider_name: string
|
||||
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dify-web",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
@@ -9,10 +9,10 @@
|
||||
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
|
||||
"build": "next build",
|
||||
"start": "cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public && cross-env PORT=$npm_config_port HOSTNAME=$npm_config_host node .next/standalone/server.js",
|
||||
"lint": "pnpm eslint",
|
||||
"lint": "pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache",
|
||||
"fix": "next lint --fix",
|
||||
"eslint-fix": "eslint --fix",
|
||||
"eslint-fix-only-show-error": "eslint --fix --quiet",
|
||||
"eslint-fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix",
|
||||
"eslint-fix-only-show-error": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix --quiet",
|
||||
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky",
|
||||
"gen-icons": "node ./app/components/base/icons/script.mjs",
|
||||
"uglify-embed": "node ./bin/uglify-embed",
|
||||
@@ -61,6 +61,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"decimal.js": "^10.4.3",
|
||||
"dompurify": "^3.2.4",
|
||||
"echarts": "^5.5.1",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"elkjs": "^0.9.3",
|
||||
@@ -132,10 +133,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.1.1",
|
||||
"@eslint/js": "^9.20.0",
|
||||
"@chromatic-com/storybook": "^3.1.0",
|
||||
"@eslint-react/eslint-plugin": "^1.15.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.20.0",
|
||||
"@faker-js/faker": "^9.0.3",
|
||||
"@next/eslint-plugin-next": "^15.2.3",
|
||||
"@rgrove/parse-xml": "^4.1.0",
|
||||
@@ -174,11 +175,11 @@
|
||||
"code-inspector-plugin": "^0.18.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-config-next": "^15.0.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"eslint-plugin-storybook": "^0.11.2",
|
||||
"eslint-plugin-tailwindcss": "^3.18.0",
|
||||
"eslint-config-next": "^15.0.0",
|
||||
"husky": "^9.1.6",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
|
||||
Generated
+7
-4
@@ -121,6 +121,9 @@ importers:
|
||||
decimal.js:
|
||||
specifier: ^10.4.3
|
||||
version: 10.4.3
|
||||
dompurify:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4
|
||||
echarts:
|
||||
specifier: ^5.5.1
|
||||
version: 5.5.1
|
||||
@@ -4299,8 +4302,8 @@ packages:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.2.3:
|
||||
resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==}
|
||||
dompurify@3.2.4:
|
||||
resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==}
|
||||
|
||||
domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
@@ -13070,7 +13073,7 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.2.3:
|
||||
dompurify@3.2.4:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
@@ -15688,7 +15691,7 @@ snapshots:
|
||||
d3-sankey: 0.12.3
|
||||
dagre-d3-es: 7.0.11
|
||||
dayjs: 1.11.13
|
||||
dompurify: 3.2.3
|
||||
dompurify: 3.2.4
|
||||
katex: 0.16.21
|
||||
khroma: 2.1.0
|
||||
lodash-es: 4.17.21
|
||||
|
||||
Reference in New Issue
Block a user