mirror of https://github.com/langgenius/dify.git
merge main
This commit is contained in:
commit
e19a07c2e6
|
|
@ -5,7 +5,6 @@ from flask_restful import Resource, fields, marshal_with, reqparse
|
|||
from flask_restful.inputs import int_range
|
||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||
|
||||
import services
|
||||
from controllers.console import api
|
||||
from controllers.console.app.error import (
|
||||
CompletionRequestError,
|
||||
|
|
@ -133,7 +132,7 @@ class MessageFeedbackApi(Resource):
|
|||
rating=args.get("rating"),
|
||||
content=None,
|
||||
)
|
||||
except services.errors.message.MessageNotExistsError:
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
return {"result": "success"}
|
||||
|
|
|
|||
|
|
@ -113,9 +113,3 @@ class MemberNotInTenantError(BaseHTTPException):
|
|||
error_code = "member_not_in_tenant"
|
||||
description = "The member is not in the workspace."
|
||||
code = 400
|
||||
|
||||
|
||||
class AccountInFreezeError(BaseHTTPException):
|
||||
error_code = "account_in_freeze"
|
||||
description = "This email is temporarily unavailable."
|
||||
code = 400
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from flask_restful import marshal_with, reqparse
|
|||
from flask_restful.inputs import int_range
|
||||
from werkzeug.exceptions import InternalServerError, NotFound
|
||||
|
||||
import services
|
||||
from controllers.console.app.error import (
|
||||
AppMoreLikeThisDisabledError,
|
||||
CompletionRequestError,
|
||||
|
|
@ -29,7 +28,11 @@ from models.model import AppMode
|
|||
from services.app_generate_service import AppGenerateService
|
||||
from services.errors.app import MoreLikeThisDisabledError
|
||||
from services.errors.conversation import ConversationNotExistsError
|
||||
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
|
||||
from services.errors.message import (
|
||||
FirstMessageNotExistsError,
|
||||
MessageNotExistsError,
|
||||
SuggestedQuestionsAfterAnswerDisabledError,
|
||||
)
|
||||
from services.message_service import MessageService
|
||||
|
||||
|
||||
|
|
@ -52,9 +55,9 @@ class MessageListApi(InstalledAppResource):
|
|||
return MessageService.pagination_by_first_id(
|
||||
app_model, current_user, args["conversation_id"], args["first_id"], args["limit"]
|
||||
)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
except ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.message.FirstMessageNotExistsError:
|
||||
except FirstMessageNotExistsError:
|
||||
raise NotFound("First Message Not Exists.")
|
||||
|
||||
|
||||
|
|
@ -77,7 +80,7 @@ class MessageFeedbackApi(InstalledAppResource):
|
|||
rating=args.get("rating"),
|
||||
content=args.get("content"),
|
||||
)
|
||||
except services.errors.message.MessageNotExistsError:
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
return {"result": "success"}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,13 @@ from configs import dify_config
|
|||
from constants.languages import supported_language
|
||||
from controllers.console import api
|
||||
from controllers.console.auth.error import (
|
||||
AccountInFreezeError,
|
||||
EmailAlreadyInUseError,
|
||||
EmailChangeLimitError,
|
||||
EmailCodeError,
|
||||
InvalidEmailError,
|
||||
InvalidTokenError,
|
||||
)
|
||||
from controllers.console.error import AccountNotFound, EmailSendIpLimitError
|
||||
from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
|
||||
from controllers.console.workspace.error import (
|
||||
AccountAlreadyInitedError,
|
||||
CurrentPasswordIncorrectError,
|
||||
|
|
@ -496,7 +495,7 @@ class ChangeEmailResetApi(Resource):
|
|||
if current_user.email != old_email:
|
||||
raise AccountNotFound()
|
||||
|
||||
updated_account = AccountService.update_account(current_user, email=args["new_email"])
|
||||
updated_account = AccountService.update_account_email(current_user, email=args["new_email"])
|
||||
|
||||
AccountService.send_change_email_completed_notify_email(
|
||||
email=args["new_email"],
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ from fields.message_fields import agent_thought_fields, feedback_fields
|
|||
from fields.raws import FilesContainedField
|
||||
from libs.helper import TimestampField, uuid_value
|
||||
from models.model import App, AppMode, EndUser
|
||||
from services.errors.message import SuggestedQuestionsAfterAnswerDisabledError
|
||||
from services.errors.message import (
|
||||
FirstMessageNotExistsError,
|
||||
MessageNotExistsError,
|
||||
SuggestedQuestionsAfterAnswerDisabledError,
|
||||
)
|
||||
from services.message_service import MessageService
|
||||
|
||||
|
||||
|
|
@ -65,7 +69,7 @@ class MessageListApi(Resource):
|
|||
)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.message.FirstMessageNotExistsError:
|
||||
except FirstMessageNotExistsError:
|
||||
raise NotFound("First Message Not Exists.")
|
||||
|
||||
|
||||
|
|
@ -87,7 +91,7 @@ class MessageFeedbackApi(Resource):
|
|||
rating=args.get("rating"),
|
||||
content=args.get("content"),
|
||||
)
|
||||
except services.errors.message.MessageNotExistsError:
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
return {"result": "success"}
|
||||
|
|
@ -117,7 +121,7 @@ class MessageSuggestedApi(Resource):
|
|||
questions = MessageService.get_suggested_questions_after_answer(
|
||||
app_model=app_model, user=end_user, message_id=message_id, invoke_from=InvokeFrom.SERVICE_API
|
||||
)
|
||||
except services.errors.message.MessageNotExistsError:
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message Not Exists.")
|
||||
except SuggestedQuestionsAfterAnswerDisabledError:
|
||||
raise BadRequest("Suggested Questions Is Disabled.")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from flask_restful import fields, marshal_with, reqparse
|
|||
from flask_restful.inputs import int_range
|
||||
from werkzeug.exceptions import InternalServerError, NotFound
|
||||
|
||||
import services
|
||||
from controllers.web import api
|
||||
from controllers.web.error import (
|
||||
AppMoreLikeThisDisabledError,
|
||||
|
|
@ -29,7 +28,11 @@ from models.model import AppMode
|
|||
from services.app_generate_service import AppGenerateService
|
||||
from services.errors.app import MoreLikeThisDisabledError
|
||||
from services.errors.conversation import ConversationNotExistsError
|
||||
from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError
|
||||
from services.errors.message import (
|
||||
FirstMessageNotExistsError,
|
||||
MessageNotExistsError,
|
||||
SuggestedQuestionsAfterAnswerDisabledError,
|
||||
)
|
||||
from services.message_service import MessageService
|
||||
|
||||
|
||||
|
|
@ -73,9 +76,9 @@ class MessageListApi(WebApiResource):
|
|||
return MessageService.pagination_by_first_id(
|
||||
app_model, end_user, args["conversation_id"], args["first_id"], args["limit"]
|
||||
)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
except ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.message.FirstMessageNotExistsError:
|
||||
except FirstMessageNotExistsError:
|
||||
raise NotFound("First Message Not Exists.")
|
||||
|
||||
|
||||
|
|
@ -96,7 +99,7 @@ class MessageFeedbackApi(WebApiResource):
|
|||
rating=args.get("rating"),
|
||||
content=args.get("content"),
|
||||
)
|
||||
except services.errors.message.MessageNotExistsError:
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
return {"result": "success"}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ToolNodeData(BaseNodeData, ToolEntity):
|
|||
if not isinstance(val, str):
|
||||
raise ValueError("value must be a list of strings")
|
||||
elif typ == "constant" and not isinstance(value, str | int | float | bool | dict):
|
||||
raise ValueError("value must be a string, int, float, or bool")
|
||||
raise ValueError("value must be a string, int, float, bool or dict")
|
||||
return typ
|
||||
|
||||
tool_parameters: dict[str, ToolInput]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "dify-api"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
requires-python = ">=3.11,<3.13"
|
||||
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -355,6 +355,17 @@ class AccountService:
|
|||
db.session.commit()
|
||||
return account
|
||||
|
||||
@staticmethod
|
||||
def update_account_email(account: Account, email: str) -> Account:
|
||||
"""Update account email"""
|
||||
account.email = email
|
||||
account_integrate = db.session.query(AccountIntegrate).filter_by(account_id=account.id).first()
|
||||
if account_integrate:
|
||||
db.session.delete(account_integrate)
|
||||
db.session.add(account)
|
||||
db.session.commit()
|
||||
return account
|
||||
|
||||
@staticmethod
|
||||
def update_login_info(account: Account, *, ip_address: str) -> None:
|
||||
"""Update last login time and ip"""
|
||||
|
|
|
|||
|
|
@ -53,9 +53,10 @@ class AppService:
|
|||
if args.get("name"):
|
||||
name = args["name"][:30]
|
||||
filters.append(App.name.ilike(f"%{name}%"))
|
||||
if args.get("tag_ids"):
|
||||
# Check if tag_ids is not empty to avoid WHERE false condition
|
||||
if args.get("tag_ids") and len(args["tag_ids"]) > 0:
|
||||
target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"])
|
||||
if target_ids:
|
||||
if target_ids and len(target_ids) > 0:
|
||||
filters.append(App.id.in_(target_ids))
|
||||
else:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@ class ConversationService:
|
|||
Conversation.from_account_id == (user.id if isinstance(user, Account) else None),
|
||||
or_(Conversation.invoke_from.is_(None), Conversation.invoke_from == invoke_from.value),
|
||||
)
|
||||
if include_ids is not None:
|
||||
# Check if include_ids is not None and not empty to avoid WHERE false condition
|
||||
if include_ids is not None and len(include_ids) > 0:
|
||||
stmt = stmt.where(Conversation.id.in_(include_ids))
|
||||
if exclude_ids is not None:
|
||||
# Check if exclude_ids is not None and not empty to avoid WHERE false condition
|
||||
if exclude_ids is not None and len(exclude_ids) > 0:
|
||||
stmt = stmt.where(~Conversation.id.in_(exclude_ids))
|
||||
|
||||
# define sort fields and directions
|
||||
|
|
|
|||
|
|
@ -97,14 +97,16 @@ class DatasetService:
|
|||
|
||||
if user.current_role == TenantAccountRole.DATASET_OPERATOR:
|
||||
# only show datasets that the user has permission to access
|
||||
if permitted_dataset_ids:
|
||||
# Check if permitted_dataset_ids is not empty to avoid WHERE false condition
|
||||
if permitted_dataset_ids and len(permitted_dataset_ids) > 0:
|
||||
query = query.where(Dataset.id.in_(permitted_dataset_ids))
|
||||
else:
|
||||
return [], 0
|
||||
else:
|
||||
if user.current_role != TenantAccountRole.OWNER or not include_all:
|
||||
# show all datasets that the user has permission to access
|
||||
if permitted_dataset_ids:
|
||||
# Check if permitted_dataset_ids is not empty to avoid WHERE false condition
|
||||
if permitted_dataset_ids and len(permitted_dataset_ids) > 0:
|
||||
query = query.where(
|
||||
db.or_(
|
||||
Dataset.permission == DatasetPermissionEnum.ALL_TEAM,
|
||||
|
|
@ -133,9 +135,10 @@ class DatasetService:
|
|||
if search:
|
||||
query = query.where(Dataset.name.ilike(f"%{search}%"))
|
||||
|
||||
if tag_ids:
|
||||
# Check if tag_ids is not empty to avoid WHERE false condition
|
||||
if tag_ids and len(tag_ids) > 0:
|
||||
target_ids = TagService.get_target_ids_by_tag_ids("knowledge", tenant_id, tag_ids)
|
||||
if target_ids:
|
||||
if target_ids and len(target_ids) > 0:
|
||||
query = query.where(Dataset.id.in_(target_ids))
|
||||
else:
|
||||
return [], 0
|
||||
|
|
@ -164,6 +167,9 @@ class DatasetService:
|
|||
|
||||
@staticmethod
|
||||
def get_datasets_by_ids(ids, tenant_id):
|
||||
# Check if ids is not empty to avoid WHERE false condition
|
||||
if not ids or len(ids) == 0:
|
||||
return [], 0
|
||||
stmt = select(Dataset).where(Dataset.id.in_(ids), Dataset.tenant_id == tenant_id)
|
||||
|
||||
datasets = db.paginate(select=stmt, page=1, per_page=len(ids), max_per_page=len(ids), error_out=False)
|
||||
|
|
@ -1132,6 +1138,9 @@ class DocumentService:
|
|||
|
||||
@staticmethod
|
||||
def delete_documents(dataset: Dataset, document_ids: list[str]):
|
||||
# Check if document_ids is not empty to avoid WHERE false condition
|
||||
if not document_ids or len(document_ids) == 0:
|
||||
return
|
||||
documents = db.session.query(Document).where(Document.id.in_(document_ids)).all()
|
||||
file_ids = [
|
||||
document.data_source_info_dict["upload_file_id"]
|
||||
|
|
@ -2778,6 +2787,9 @@ class SegmentService:
|
|||
|
||||
@classmethod
|
||||
def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset):
|
||||
# Check if segment_ids is not empty to avoid WHERE false condition
|
||||
if not segment_ids or len(segment_ids) == 0:
|
||||
return
|
||||
index_node_ids = (
|
||||
db.session.query(DocumentSegment)
|
||||
.with_entities(DocumentSegment.index_node_id)
|
||||
|
|
@ -2797,6 +2809,9 @@ class SegmentService:
|
|||
|
||||
@classmethod
|
||||
def update_segments_status(cls, segment_ids: list, action: str, dataset: Dataset, document: Document):
|
||||
# Check if segment_ids is not empty to avoid WHERE false condition
|
||||
if not segment_ids or len(segment_ids) == 0:
|
||||
return
|
||||
if action == "enable":
|
||||
segments = (
|
||||
db.session.query(DocumentSegment)
|
||||
|
|
@ -3058,7 +3073,8 @@ class SegmentService:
|
|||
DocumentSegment.document_id == document_id, DocumentSegment.tenant_id == tenant_id
|
||||
)
|
||||
|
||||
if status_list:
|
||||
# Check if status_list is not empty to avoid WHERE false condition
|
||||
if status_list and len(status_list) > 0:
|
||||
query = query.where(DocumentSegment.status.in_(status_list))
|
||||
|
||||
if keyword:
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ class MessageService:
|
|||
|
||||
base_query = base_query.where(Message.conversation_id == conversation.id)
|
||||
|
||||
if include_ids is not None:
|
||||
# Check if include_ids is not None and not empty to avoid WHERE false condition
|
||||
if include_ids is not None and len(include_ids) > 0:
|
||||
base_query = base_query.where(Message.id.in_(include_ids))
|
||||
|
||||
if last_id:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ class TagService:
|
|||
|
||||
@staticmethod
|
||||
def get_target_ids_by_tag_ids(tag_type: str, current_tenant_id: str, tag_ids: list) -> list:
|
||||
# Check if tag_ids is not empty to avoid WHERE false condition
|
||||
if not tag_ids or len(tag_ids) == 0:
|
||||
return []
|
||||
tags = (
|
||||
db.session.query(Tag)
|
||||
.where(Tag.id.in_(tag_ids), Tag.tenant_id == current_tenant_id, Tag.type == tag_type)
|
||||
|
|
@ -34,6 +37,9 @@ class TagService:
|
|||
if not tags:
|
||||
return []
|
||||
tag_ids = [tag.id for tag in tags]
|
||||
# Check if tag_ids is not empty to avoid WHERE false condition
|
||||
if not tag_ids or len(tag_ids) == 0:
|
||||
return []
|
||||
tag_bindings = (
|
||||
db.session.query(TagBinding.target_id)
|
||||
.where(TagBinding.tag_id.in_(tag_ids), TagBinding.tenant_id == current_tenant_id)
|
||||
|
|
|
|||
|
|
@ -24,79 +24,83 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str]):
|
|||
"""
|
||||
documents: list[Document] = []
|
||||
start_at = time.perf_counter()
|
||||
try:
|
||||
dataset = db.session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
logging.info(click.style(f"Dataset not found: {dataset_id}", fg="red"))
|
||||
return
|
||||
tenant_id = dataset.tenant_id
|
||||
for document_id in document_ids:
|
||||
retry_indexing_cache_key = f"document_{document_id}_is_retried"
|
||||
# check document limit
|
||||
features = FeatureService.get_features(tenant_id)
|
||||
try:
|
||||
if features.billing.enabled:
|
||||
vector_space = features.vector_space
|
||||
if 0 < vector_space.limit <= vector_space.size:
|
||||
raise ValueError(
|
||||
"Your total number of documents plus the number of uploads have over the limit of "
|
||||
"your subscription."
|
||||
)
|
||||
except Exception as e:
|
||||
document = (
|
||||
db.session.query(Document)
|
||||
.where(Document.id == document_id, Document.dataset_id == dataset_id)
|
||||
.first()
|
||||
)
|
||||
if document:
|
||||
document.indexing_status = "error"
|
||||
document.error = str(e)
|
||||
document.stopped_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||
db.session.add(document)
|
||||
db.session.commit()
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
return
|
||||
|
||||
dataset = db.session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
if not dataset:
|
||||
logging.info(click.style(f"Dataset not found: {dataset_id}", fg="red"))
|
||||
db.session.close()
|
||||
return
|
||||
tenant_id = dataset.tenant_id
|
||||
for document_id in document_ids:
|
||||
retry_indexing_cache_key = f"document_{document_id}_is_retried"
|
||||
# check document limit
|
||||
features = FeatureService.get_features(tenant_id)
|
||||
try:
|
||||
if features.billing.enabled:
|
||||
vector_space = features.vector_space
|
||||
if 0 < vector_space.limit <= vector_space.size:
|
||||
raise ValueError(
|
||||
"Your total number of documents plus the number of uploads have over the limit of "
|
||||
"your subscription."
|
||||
)
|
||||
except Exception as e:
|
||||
logging.info(click.style(f"Start retry document: {document_id}", fg="green"))
|
||||
document = (
|
||||
db.session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first()
|
||||
)
|
||||
if document:
|
||||
if not document:
|
||||
logging.info(click.style(f"Document not found: {document_id}", fg="yellow"))
|
||||
return
|
||||
try:
|
||||
# clean old data
|
||||
index_processor = IndexProcessorFactory(document.doc_form).init_index_processor()
|
||||
|
||||
segments = db.session.query(DocumentSegment).where(DocumentSegment.document_id == document_id).all()
|
||||
if segments:
|
||||
index_node_ids = [segment.index_node_id for segment in segments]
|
||||
# delete from vector index
|
||||
index_processor.clean(dataset, index_node_ids, with_keywords=True, delete_child_chunks=True)
|
||||
|
||||
for segment in segments:
|
||||
db.session.delete(segment)
|
||||
db.session.commit()
|
||||
|
||||
document.indexing_status = "parsing"
|
||||
document.processing_started_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||
db.session.add(document)
|
||||
db.session.commit()
|
||||
|
||||
indexing_runner = IndexingRunner()
|
||||
indexing_runner.run([document])
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
except Exception as ex:
|
||||
document.indexing_status = "error"
|
||||
document.error = str(e)
|
||||
document.error = str(ex)
|
||||
document.stopped_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||
db.session.add(document)
|
||||
db.session.commit()
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
db.session.close()
|
||||
return
|
||||
|
||||
logging.info(click.style(f"Start retry document: {document_id}", fg="green"))
|
||||
document = (
|
||||
db.session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first()
|
||||
logging.info(click.style(str(ex), fg="yellow"))
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
logging.exception("retry_document_indexing_task failed, document_id: %s", document_id)
|
||||
end_at = time.perf_counter()
|
||||
logging.info(click.style(f"Retry dataset: {dataset_id} latency: {end_at - start_at}", fg="green"))
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
"retry_document_indexing_task failed, dataset_id: %s, document_ids: %s", dataset_id, document_ids
|
||||
)
|
||||
if not document:
|
||||
logging.info(click.style(f"Document not found: {document_id}", fg="yellow"))
|
||||
db.session.close()
|
||||
return
|
||||
try:
|
||||
# clean old data
|
||||
index_processor = IndexProcessorFactory(document.doc_form).init_index_processor()
|
||||
|
||||
segments = db.session.query(DocumentSegment).where(DocumentSegment.document_id == document_id).all()
|
||||
if segments:
|
||||
index_node_ids = [segment.index_node_id for segment in segments]
|
||||
# delete from vector index
|
||||
index_processor.clean(dataset, index_node_ids, with_keywords=True, delete_child_chunks=True)
|
||||
|
||||
for segment in segments:
|
||||
db.session.delete(segment)
|
||||
db.session.commit()
|
||||
|
||||
document.indexing_status = "parsing"
|
||||
document.processing_started_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||
db.session.add(document)
|
||||
db.session.commit()
|
||||
|
||||
indexing_runner = IndexingRunner()
|
||||
indexing_runner.run([document])
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
except Exception as ex:
|
||||
document.indexing_status = "error"
|
||||
document.error = str(ex)
|
||||
document.stopped_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
||||
db.session.add(document)
|
||||
db.session.commit()
|
||||
logging.info(click.style(str(ex), fg="yellow"))
|
||||
redis_client.delete(retry_indexing_cache_key)
|
||||
logging.exception("retry_document_indexing_task failed, document_id: %s", document_id)
|
||||
finally:
|
||||
db.session.close()
|
||||
end_at = time.perf_counter()
|
||||
logging.info(click.style(f"Retry dataset: {dataset_id} latency: {end_at - start_at}", fg="green"))
|
||||
raise e
|
||||
finally:
|
||||
db.session.close()
|
||||
|
|
|
|||
|
|
@ -1217,7 +1217,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "dify-api"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "arize-phoenix-otel" },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -31,7 +31,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -58,7 +58,7 @@ services:
|
|||
# worker_beat service
|
||||
# Celery beat for scheduling periodic tasks.
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -76,7 +76,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.7.0
|
||||
image: langgenius/dify-web:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -571,7 +571,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -598,7 +598,7 @@ services:
|
|||
# worker_beat service
|
||||
# Celery beat for scheduling periodic tasks.
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.7.0
|
||||
image: langgenius/dify-api:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -616,7 +616,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.7.0
|
||||
image: langgenius/dify-web:1.7.1
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 71 KiB |
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* Test suite for verifying upload feature translations across all locales
|
||||
* Specifically tests for issue #23062: Missing Upload feature translations (esp. audioUpload) across most locales
|
||||
*/
|
||||
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
// Get all supported locales from the i18n directory
|
||||
const I18N_DIR = path.join(__dirname, '../i18n')
|
||||
const getSupportedLocales = (): string[] => {
|
||||
return fs.readdirSync(I18N_DIR)
|
||||
.filter(item => fs.statSync(path.join(I18N_DIR, item)).isDirectory())
|
||||
.sort()
|
||||
}
|
||||
|
||||
// Helper function to load translation file content
|
||||
const loadTranslationContent = (locale: string): string => {
|
||||
const filePath = path.join(I18N_DIR, locale, 'app-debug.ts')
|
||||
|
||||
if (!fs.existsSync(filePath))
|
||||
throw new Error(`Translation file not found: ${filePath}`)
|
||||
|
||||
return fs.readFileSync(filePath, 'utf-8')
|
||||
}
|
||||
|
||||
// Helper function to check if upload features exist
|
||||
const hasUploadFeatures = (content: string): { [key: string]: boolean } => {
|
||||
return {
|
||||
fileUpload: /fileUpload\s*:\s*{/.test(content),
|
||||
imageUpload: /imageUpload\s*:\s*{/.test(content),
|
||||
documentUpload: /documentUpload\s*:\s*{/.test(content),
|
||||
audioUpload: /audioUpload\s*:\s*{/.test(content),
|
||||
featureBar: /bar\s*:\s*{/.test(content),
|
||||
}
|
||||
}
|
||||
|
||||
describe('Upload Features i18n Translations - Issue #23062', () => {
|
||||
let supportedLocales: string[]
|
||||
|
||||
beforeAll(() => {
|
||||
supportedLocales = getSupportedLocales()
|
||||
console.log(`Testing ${supportedLocales.length} locales for upload features`)
|
||||
})
|
||||
|
||||
test('all locales should have translation files', () => {
|
||||
supportedLocales.forEach((locale) => {
|
||||
const filePath = path.join(I18N_DIR, locale, 'app-debug.ts')
|
||||
expect(fs.existsSync(filePath)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test('all locales should have required upload features', () => {
|
||||
const results: { [locale: string]: { [feature: string]: boolean } } = {}
|
||||
|
||||
supportedLocales.forEach((locale) => {
|
||||
const content = loadTranslationContent(locale)
|
||||
const features = hasUploadFeatures(content)
|
||||
results[locale] = features
|
||||
|
||||
// Check that all upload features exist
|
||||
expect(features.fileUpload).toBe(true)
|
||||
expect(features.imageUpload).toBe(true)
|
||||
expect(features.documentUpload).toBe(true)
|
||||
expect(features.audioUpload).toBe(true)
|
||||
expect(features.featureBar).toBe(true)
|
||||
})
|
||||
|
||||
console.log('✅ All locales have complete upload features')
|
||||
})
|
||||
|
||||
test('previously missing locales should now have audioUpload - Issue #23062', () => {
|
||||
// These locales were specifically missing audioUpload
|
||||
const previouslyMissingLocales = ['fa-IR', 'hi-IN', 'ro-RO', 'sl-SI', 'th-TH', 'uk-UA', 'vi-VN']
|
||||
|
||||
previouslyMissingLocales.forEach((locale) => {
|
||||
const content = loadTranslationContent(locale)
|
||||
|
||||
// Verify audioUpload exists
|
||||
expect(/audioUpload\s*:\s*{/.test(content)).toBe(true)
|
||||
|
||||
// Verify it has title and description
|
||||
expect(/audioUpload[^}]*title\s*:/.test(content)).toBe(true)
|
||||
expect(/audioUpload[^}]*description\s*:/.test(content)).toBe(true)
|
||||
|
||||
console.log(`✅ ${locale} - Issue #23062 resolved: audioUpload feature present`)
|
||||
})
|
||||
})
|
||||
|
||||
test('upload features should have required properties', () => {
|
||||
supportedLocales.forEach((locale) => {
|
||||
const content = loadTranslationContent(locale)
|
||||
|
||||
// Check fileUpload has required properties
|
||||
if (/fileUpload\s*:\s*{/.test(content)) {
|
||||
expect(/fileUpload[^}]*title\s*:/.test(content)).toBe(true)
|
||||
expect(/fileUpload[^}]*description\s*:/.test(content)).toBe(true)
|
||||
}
|
||||
|
||||
// Check imageUpload has required properties
|
||||
if (/imageUpload\s*:\s*{/.test(content)) {
|
||||
expect(/imageUpload[^}]*title\s*:/.test(content)).toBe(true)
|
||||
expect(/imageUpload[^}]*description\s*:/.test(content)).toBe(true)
|
||||
}
|
||||
|
||||
// Check documentUpload has required properties
|
||||
if (/documentUpload\s*:\s*{/.test(content)) {
|
||||
expect(/documentUpload[^}]*title\s*:/.test(content)).toBe(true)
|
||||
expect(/documentUpload[^}]*description\s*:/.test(content)).toBe(true)
|
||||
}
|
||||
|
||||
// Check audioUpload has required properties
|
||||
if (/audioUpload\s*:\s*{/.test(content)) {
|
||||
expect(/audioUpload[^}]*title\s*:/.test(content)).toBe(true)
|
||||
expect(/audioUpload[^}]*description\s*:/.test(content)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -88,10 +88,10 @@ const HeaderOptions: FC<Props> = ({
|
|||
await clearAllAnnotations(appId)
|
||||
onAdded()
|
||||
}
|
||||
catch (_) {
|
||||
catch (_) {
|
||||
}
|
||||
finally {
|
||||
setShowClearConfirm(false)
|
||||
finally {
|
||||
setShowClearConfirm(false)
|
||||
}
|
||||
}
|
||||
const Operations = () => {
|
||||
|
|
@ -146,7 +146,7 @@ catch (_) {
|
|||
onClick={handleClearAll}
|
||||
className='mx-1 flex h-9 w-[calc(100%_-_8px)] cursor-pointer items-center space-x-2 rounded-lg px-3 py-2 text-red-600 hover:bg-red-50 disabled:opacity-50'
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4'/>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
<span className='system-sm-regular grow text-left'>
|
||||
{t('appAnnotation.table.header.clearAll')}
|
||||
</span>
|
||||
|
|
@ -168,11 +168,9 @@ catch (_) {
|
|||
position="br"
|
||||
trigger="click"
|
||||
btnElement={
|
||||
<Button variant='secondary' className='w-8 p-0'>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
</Button>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
}
|
||||
btnClassName='p-0 border-0'
|
||||
btnClassName='btn btn-secondary btn-medium w-8 p-0'
|
||||
className={'!z-20 h-fit !w-[155px]'}
|
||||
popupClassName='!w-full !overflow-visible'
|
||||
manualClose
|
||||
|
|
@ -196,12 +194,12 @@ catch (_) {
|
|||
)
|
||||
}
|
||||
{
|
||||
showClearConfirm && (
|
||||
<ClearAllAnnotationsConfirmModal
|
||||
isShow={showClearConfirm}
|
||||
onHide={() => setShowClearConfirm(false)}
|
||||
onConfirm={handleConfirmed}
|
||||
/>
|
||||
showClearConfirm && (
|
||||
<ClearAllAnnotationsConfirmModal
|
||||
isShow={showClearConfirm}
|
||||
onHide={() => setShowClearConfirm(false)}
|
||||
onConfirm={handleConfirmed}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ const WarningMask: FC<IWarningMaskProps> = ({
|
|||
footer,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${s.mask} absolute inset-0 z-10 pt-16`}
|
||||
<div className={`${s.mask} absolute inset-0 z-10 bg-components-panel-bg-blur pt-16`}
|
||||
>
|
||||
<div className='mx-auto px-10'>
|
||||
<div className={`${s.icon} flex h-11 w-11 items-center justify-center rounded-xl bg-white`}>{warningIcon}</div>
|
||||
<div className='mt-4 text-[24px] font-semibold leading-normal text-gray-800'>
|
||||
<div className={`${s.icon} flex h-11 w-11 items-center justify-center rounded-xl bg-components-panel-bg`}>{warningIcon}</div>
|
||||
<div className='mt-4 text-[24px] font-semibold leading-normal text-text-primary'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='mt-3 text-base text-gray-500'>
|
||||
<div className='mt-3 text-base text-text-secondary'>
|
||||
{description}
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
.mask {
|
||||
background-color: rgba(239, 244, 255, 0.9);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
|||
<SimpleSelect
|
||||
key={`default-select-${options.join('-')}`}
|
||||
className="w-full"
|
||||
optionWrapClassName="max-h-[140px] overflow-y-auto"
|
||||
items={[
|
||||
{ value: '', name: t('appDebug.variableConfig.noDefaultValue') },
|
||||
...options.filter(opt => opt.trim() !== '').map(option => ({
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import PureSelect from '@/app/components/base/select/pure'
|
|||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import RadioE from '@/app/components/base/radio/ui'
|
||||
|
||||
export type BaseFieldProps = {
|
||||
fieldClassName?: string
|
||||
|
|
@ -57,8 +58,27 @@ const BaseField = ({
|
|||
if (typeof placeholder === 'object' && placeholder !== null)
|
||||
return renderI18nObject(placeholder as Record<string, string>)
|
||||
}, [placeholder, renderI18nObject])
|
||||
const optionValues = useStore(field.form.store, (s) => {
|
||||
const result: Record<string, any> = {}
|
||||
options?.forEach((option) => {
|
||||
if (option.show_on?.length) {
|
||||
option.show_on.forEach((condition) => {
|
||||
result[condition.variable] = s.values[condition.variable]
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
const memorizedOptions = useMemo(() => {
|
||||
return options?.map((option) => {
|
||||
return options?.filter((option) => {
|
||||
if (!option.show_on?.length)
|
||||
return true
|
||||
|
||||
return option.show_on.every((condition) => {
|
||||
const conditionValue = optionValues[condition.variable]
|
||||
return conditionValue === condition.value
|
||||
})
|
||||
}).map((option) => {
|
||||
return {
|
||||
label: typeof option.label === 'string' ? option.label : renderI18nObject(option.label),
|
||||
value: option.value,
|
||||
|
|
@ -151,17 +171,28 @@ const BaseField = ({
|
|||
}
|
||||
{
|
||||
formSchema.type === FormTypeEnum.radio && (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<div className={cn(
|
||||
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
|
||||
)}>
|
||||
{
|
||||
memorizedOptions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
|
||||
inputClassName,
|
||||
)}
|
||||
onClick={() => field.handleChange(option.value)}
|
||||
>
|
||||
{
|
||||
formSchema.showRadioUI && (
|
||||
<RadioE
|
||||
className='mr-2'
|
||||
isChecked={value === option.value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{option.label}
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
memo,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type {
|
||||
AnyFieldApi,
|
||||
|
|
@ -45,8 +46,18 @@ const BaseForm = ({
|
|||
disabled,
|
||||
formFromProps,
|
||||
}: BaseFormProps) => {
|
||||
const initialDefaultValues = useMemo(() => {
|
||||
if (defaultValues)
|
||||
return defaultValues
|
||||
|
||||
return formSchemas.reduce((acc, schema) => {
|
||||
if (schema.default)
|
||||
acc[schema.name] = schema.default
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
}, [defaultValues])
|
||||
const formFromHook = useForm({
|
||||
defaultValues,
|
||||
defaultValues: initialDefaultValues,
|
||||
})
|
||||
const form: any = formFromProps || formFromHook
|
||||
const { getFormValues } = useGetFormValues(form, formSchemas)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const AuthForm = ({
|
|||
defaultValues,
|
||||
ref,
|
||||
formFromProps,
|
||||
...rest
|
||||
}: BaseFormProps) => {
|
||||
return (
|
||||
<BaseForm
|
||||
|
|
@ -16,6 +17,7 @@ const AuthForm = ({
|
|||
formClassName='space-y-4'
|
||||
labelClassName='h-6 flex items-center mb-1 system-sm-medium text-text-secondary'
|
||||
formFromProps={formFromProps}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export type FormSchema = {
|
|||
options?: FormOption[]
|
||||
labelClassName?: string
|
||||
validators?: AnyValidators
|
||||
showRadioUI?: boolean
|
||||
}
|
||||
|
||||
export type FormValues = Record<string, any>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ const Select: FC<ISelectProps> = ({
|
|||
defaultSelect = existed
|
||||
|
||||
setSelectedItem(defaultSelect)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultValue])
|
||||
|
||||
const filteredItems: Item[]
|
||||
|
|
@ -201,7 +200,6 @@ const SimpleSelect: FC<ISelectProps> = ({
|
|||
defaultSelect = existed
|
||||
|
||||
setSelectedItem(defaultSelect)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultValue])
|
||||
|
||||
const listboxRef = useRef<HTMLDivElement>(null)
|
||||
|
|
@ -344,7 +342,7 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
|||
>
|
||||
<span
|
||||
className={`
|
||||
grow truncate
|
||||
grow truncate text-text-secondary
|
||||
${!selectedItem?.name && 'text-components-input-text-placeholder'}
|
||||
`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||
destructive && 'border-components-input-border-destructive bg-components-input-bg-destructive text-components-input-text-filled hover:border-components-input-border-destructive hover:bg-components-input-bg-destructive focus:border-components-input-border-destructive focus:bg-components-input-bg-destructive',
|
||||
className,
|
||||
)}
|
||||
value={value}
|
||||
value={value ?? ''}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||
useEffect(() => {
|
||||
const newInputs: Record<string, any> = {}
|
||||
promptConfig.prompt_variables.forEach((item) => {
|
||||
if (item.type === 'string' || item.type === 'paragraph')
|
||||
if (item.type === 'select')
|
||||
newInputs[item.key] = item.default
|
||||
else if (item.type === 'string' || item.type === 'paragraph')
|
||||
newInputs[item.key] = ''
|
||||
else
|
||||
newInputs[item.key] = undefined
|
||||
|
|
|
|||
|
|
@ -179,8 +179,8 @@ const WorkflowToolConfigureButton = ({
|
|||
{(!published || !isLoading) && (
|
||||
<div className={cn(
|
||||
'group rounded-lg bg-background-section-burn transition-colors',
|
||||
disabled ? 'cursor-not-allowed opacity-30 shadow-xs' : 'cursor-pointer',
|
||||
!disabled && !published && 'hover:bg-state-accent-hover',
|
||||
disabled || !isCurrentWorkspaceManager ? 'cursor-not-allowed opacity-60 shadow-xs' : 'cursor-pointer',
|
||||
!disabled && !published && isCurrentWorkspaceManager && 'hover:bg-state-accent-hover',
|
||||
)}>
|
||||
{isCurrentWorkspaceManager
|
||||
? (
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ const FormInputItem: FC<Props> = ({
|
|||
<Input
|
||||
className='h-8 grow'
|
||||
type='number'
|
||||
value={varInput?.value || ''}
|
||||
value={Number.isNaN(varInput?.value) ? '' : varInput?.value}
|
||||
onChange={e => handleValueChange(e.target.value)}
|
||||
placeholder={placeholder?.[language] || placeholder?.en_US}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -64,55 +64,56 @@ const ClassList: FC<Props> = ({
|
|||
const handleSideWidth = 3
|
||||
// Todo Remove; edit topic name
|
||||
return (
|
||||
<ReactSortable
|
||||
list={list.map(item => ({ ...item }))}
|
||||
setList={handleSortTopic}
|
||||
handle='.handle'
|
||||
ghostClass='bg-components-panel-bg'
|
||||
animation={150}
|
||||
disabled={readonly}
|
||||
className='space-y-2'
|
||||
>
|
||||
{
|
||||
list.map((item, index) => {
|
||||
const canDrag = (() => {
|
||||
if (readonly)
|
||||
return false
|
||||
<>
|
||||
<ReactSortable
|
||||
list={list.map(item => ({ ...item }))}
|
||||
setList={handleSortTopic}
|
||||
handle='.handle'
|
||||
ghostClass='bg-components-panel-bg'
|
||||
animation={150}
|
||||
disabled={readonly}
|
||||
className='space-y-2'
|
||||
>
|
||||
{
|
||||
list.map((item, index) => {
|
||||
const canDrag = (() => {
|
||||
if (readonly)
|
||||
return false
|
||||
|
||||
return topicCount >= 2
|
||||
})()
|
||||
return (
|
||||
<div key={item.id}
|
||||
className={cn(
|
||||
'group relative rounded-[10px] bg-components-panel-bg',
|
||||
`-ml-${handleSideWidth} min-h-[40px] px-0 py-0`,
|
||||
)}>
|
||||
<div >
|
||||
<Item
|
||||
className={cn(canDrag && 'handle')}
|
||||
headerClassName={cn(canDrag && 'cursor-grab')}
|
||||
nodeId={nodeId}
|
||||
key={list[index].id}
|
||||
payload={item}
|
||||
onChange={handleClassChange(index)}
|
||||
onRemove={handleRemoveClass(index)}
|
||||
index={index + 1}
|
||||
readonly={readonly}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
return topicCount >= 2
|
||||
})()
|
||||
return (
|
||||
<div key={item.id}
|
||||
className={cn(
|
||||
'group relative rounded-[10px] bg-components-panel-bg',
|
||||
`-ml-${handleSideWidth} min-h-[40px] px-0 py-0`,
|
||||
)}>
|
||||
<div >
|
||||
<Item
|
||||
className={cn(canDrag && 'handle')}
|
||||
headerClassName={cn(canDrag && 'cursor-grab')}
|
||||
nodeId={nodeId}
|
||||
key={list[index].id}
|
||||
payload={item}
|
||||
onChange={handleClassChange(index)}
|
||||
onRemove={handleRemoveClass(index)}
|
||||
index={index + 1}
|
||||
readonly={readonly}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
</ReactSortable>
|
||||
{!readonly && (
|
||||
<AddButton
|
||||
onClick={handleAddClass}
|
||||
text={t(`${i18nPrefix}.addClass`)}
|
||||
/>
|
||||
)}
|
||||
|
||||
</ReactSortable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(ClassList)
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
|||
{key}
|
||||
</div>
|
||||
{typeof tool_configurations[key].value === 'string' && (
|
||||
<div title={tool_configurations[key]} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'>
|
||||
<div title={tool_configurations[key].value} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'>
|
||||
{paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value}
|
||||
</div>
|
||||
)}
|
||||
{typeof tool_configurations[key].value === 'number' && (
|
||||
<div title={tool_configurations[key].toString()} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'>
|
||||
{tool_configurations[key].value}
|
||||
<div title={Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'>
|
||||
{Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value}
|
||||
</div>
|
||||
)}
|
||||
{typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const TipPopup = ({
|
|||
}: TipPopupProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
needsDelay={false}
|
||||
offset={4}
|
||||
popupClassName='p-0 bg-transparent'
|
||||
popupContent={
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -32,9 +33,12 @@ type Props = {
|
|||
const InputsPanel = ({ onRun }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { inputs, setInputs } = useStore(s => ({
|
||||
inputs: s.inputs,
|
||||
setInputs: s.setInputs,
|
||||
}))
|
||||
const fileSettings = useFeatures(s => s.features.file)
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const inputs = useStore(s => s.inputs)
|
||||
const files = useStore(s => s.files)
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const {
|
||||
|
|
@ -44,6 +48,24 @@ const InputsPanel = ({ onRun }: Props) => {
|
|||
const startVariables = startNode?.data.variables
|
||||
const { checkInputsForm } = useCheckInputsForms()
|
||||
|
||||
const initialInputs = useMemo(() => {
|
||||
const initInputs: Record<string, any> = {}
|
||||
if (startVariables) {
|
||||
startVariables.forEach((variable) => {
|
||||
if (variable.default)
|
||||
initInputs[variable.variable] = variable.default
|
||||
})
|
||||
}
|
||||
return initInputs
|
||||
}, [startVariables])
|
||||
|
||||
useEffect(() => {
|
||||
setInputs({
|
||||
...initialInputs,
|
||||
...inputs,
|
||||
})
|
||||
}, [initialInputs])
|
||||
|
||||
const variables = useMemo(() => {
|
||||
const data = startVariables || []
|
||||
if (fileSettings?.image?.enabled) {
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
'use client'
|
||||
import { loadLangResources } from '@/i18n-config/i18next-config'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LanguagesSupported } from '@/i18n-config/language'
|
||||
|
||||
export default function I18nTest() {
|
||||
const [langs, setLangs] = useState<Lang[]>([])
|
||||
|
||||
const getLangs = useCallback(async () => {
|
||||
const langs = await genLangs()
|
||||
setLangs(langs)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
getLangs()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: 'calc(100% - 6em)',
|
||||
overflowY: 'auto',
|
||||
margin: '1em 1em 5em',
|
||||
}}
|
||||
>
|
||||
|
||||
<div style={{ minHeight: '75vh' }}>
|
||||
<h2>Summary</h2>
|
||||
|
||||
<table
|
||||
className={cn('mt-2 min-w-[340px] border-collapse border-0')}
|
||||
>
|
||||
<thead className="system-xs-medium-uppercase text-text-tertiary">
|
||||
<tr>
|
||||
<td className="w-5 min-w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1">
|
||||
#
|
||||
</td>
|
||||
<td className="w-20 min-w-20 whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
lang
|
||||
</td>
|
||||
<td className="w-20 min-w-20 whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
count
|
||||
</td>
|
||||
<td className="w-20 min-w-20 whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
missing
|
||||
</td>
|
||||
<td className="w-20 min-w-20 whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
extra
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="system-sm-regular text-text-secondary">
|
||||
{langs.map(({ locale, count, missing, extra }, idx) => <tr key={locale}>
|
||||
<td className="">{idx}</td>
|
||||
<td className="p-1.5">{locale}</td>
|
||||
<td>{count}</td>
|
||||
<td>{missing.length}</td>
|
||||
<td>{extra.length}</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Details</h2>
|
||||
|
||||
<table
|
||||
className={cn('mt-2 w-full min-w-[340px] border-collapse border-0')}
|
||||
>
|
||||
<thead className="system-xs-medium-uppercase text-text-tertiary">
|
||||
<tr>
|
||||
<td className="w-5 min-w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1">
|
||||
#
|
||||
</td>
|
||||
<td className="w-20 min-w-20 whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
lang
|
||||
</td>
|
||||
<td className="w-full whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
missing
|
||||
</td>
|
||||
<td className="w-full whitespace-nowrap bg-background-section-burn py-1.5 pl-3">
|
||||
extra
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{langs.map(({ locale, missing, extra }, idx) => {
|
||||
return (<tr key={locale}>
|
||||
<td className="py-2 align-top">{idx}</td>
|
||||
<td className="py-2 align-top">{locale}</td>
|
||||
<td className="py-2 align-top">
|
||||
<ul>
|
||||
{missing.map(key => (
|
||||
<li key={key}>{key}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
<td className="py-2 align-top">
|
||||
<ul>
|
||||
{extra.map(key => (
|
||||
<li key={key}>{key}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function genLangs() {
|
||||
const langs_: Lang[] = []
|
||||
let en!: Lang
|
||||
|
||||
const resources: Record<string, any> = {}
|
||||
// Initialize empty resource object
|
||||
for (const lang of LanguagesSupported)
|
||||
resources[lang] = await loadLangResources(lang)
|
||||
|
||||
for (const [key, value] of Object.entries(resources)) {
|
||||
const keys = getNestedKeys(value.translation)
|
||||
const lang: Lang = {
|
||||
locale: key,
|
||||
keys: new Set(keys),
|
||||
count: keys.length,
|
||||
missing: [],
|
||||
extra: [],
|
||||
}
|
||||
|
||||
langs_.push(lang)
|
||||
if (key === 'en-US') en = lang
|
||||
}
|
||||
|
||||
for (const lang of langs_) {
|
||||
const missing: string[] = []
|
||||
const extra: string[] = []
|
||||
|
||||
for (const key of lang.keys)
|
||||
if (!en.keys.has(key)) extra.push(key)
|
||||
|
||||
for (const key of en.keys)
|
||||
if (!lang.keys.has(key)) missing.push(key)
|
||||
|
||||
lang.missing = missing
|
||||
lang.extra = extra
|
||||
}
|
||||
return langs_
|
||||
}
|
||||
|
||||
function getNestedKeys(translation: Record<string, any>): string[] {
|
||||
const nestedKeys: string[] = []
|
||||
const iterateKeys = (obj: Record<string, any>, prefix = '') => {
|
||||
for (const key in obj) {
|
||||
const nestedKey = prefix ? `${prefix}.${key}` : key
|
||||
// nestedKeys.push(nestedKey);
|
||||
if (typeof obj[key] === 'object') iterateKeys(obj[key], nestedKey)
|
||||
else if (typeof obj[key] === 'string') nestedKeys.push(nestedKey)
|
||||
}
|
||||
}
|
||||
iterateKeys(translation)
|
||||
return nestedKeys
|
||||
}
|
||||
|
||||
type Lang = {
|
||||
locale: string;
|
||||
keys: Set<string>;
|
||||
count: number;
|
||||
missing: string[];
|
||||
extra: string[];
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import type React from 'react'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export default async function Layout({ children }: React.PropsWithChildren) {
|
||||
if (process.env.NODE_ENV !== 'development')
|
||||
notFound()
|
||||
|
||||
return children
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const transpile = require('typescript').transpile
|
||||
const magicast = require('magicast')
|
||||
const { parseModule, generateCode, loadFile } = magicast
|
||||
|
|
@ -8,6 +9,7 @@ const { translate } = bingTranslate
|
|||
const data = require('./languages.json')
|
||||
|
||||
const targetLanguage = 'en-US'
|
||||
const i18nFolder = '../i18n' // Path to i18n folder relative to this script
|
||||
// https://github.com/plainheart/bing-translate-api/blob/master/src/met/lang.json
|
||||
const languageKeyMap = data.languages.reduce((map, language) => {
|
||||
if (language.supported) {
|
||||
|
|
@ -21,11 +23,16 @@ const languageKeyMap = data.languages.reduce((map, language) => {
|
|||
}, {})
|
||||
|
||||
async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) {
|
||||
const skippedKeys = []
|
||||
const translatedKeys = []
|
||||
|
||||
await Promise.all(Object.keys(sourceObj).map(async (key) => {
|
||||
if (targetObject[key] === undefined) {
|
||||
if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = {}
|
||||
await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
const result = await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
skippedKeys.push(...result.skipped)
|
||||
translatedKeys.push(...result.translated)
|
||||
}
|
||||
else {
|
||||
try {
|
||||
|
|
@ -34,75 +41,198 @@ async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) {
|
|||
targetObject[key] = ''
|
||||
return
|
||||
}
|
||||
// not support translate with '(' or ')'
|
||||
if (source.includes('(') || source.includes(')'))
|
||||
return
|
||||
|
||||
// Only skip obvious code patterns, not normal text with parentheses
|
||||
const codePatterns = [
|
||||
/\{\{.*\}\}/, // Template variables like {{key}}
|
||||
/\$\{.*\}/, // Template literals ${...}
|
||||
/<[^>]+>/, // HTML/XML tags
|
||||
/function\s*\(/, // Function definitions
|
||||
/=\s*\(/, // Assignment with function calls
|
||||
]
|
||||
|
||||
const isCodeLike = codePatterns.some(pattern => pattern.test(source))
|
||||
if (isCodeLike) {
|
||||
console.log(`⏭️ Skipping code-like content: "${source.substring(0, 50)}..."`)
|
||||
skippedKeys.push(`${key}: ${source}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🔄 Translating: "${source}" to ${toLanguage}`)
|
||||
const { translation } = await translate(sourceObj[key], null, languageKeyMap[toLanguage])
|
||||
targetObject[key] = translation
|
||||
translatedKeys.push(`${key}: ${translation}`)
|
||||
console.log(`✅ Translated: "${translation}"`)
|
||||
}
|
||||
catch {
|
||||
console.error(`Error translating "${sourceObj[key]}" to ${toLanguage}. Key: ${key}`)
|
||||
catch (error) {
|
||||
console.error(`❌ Error translating "${sourceObj[key]}" to ${toLanguage}. Key: ${key}`, error.message)
|
||||
skippedKeys.push(`${key}: ${sourceObj[key]} (Error: ${error.message})`)
|
||||
|
||||
// Add retry mechanism for network errors
|
||||
if (error.message.includes('network') || error.message.includes('timeout')) {
|
||||
console.log(`🔄 Retrying translation for key: ${key}`)
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // Wait 1 second
|
||||
const { translation } = await translate(sourceObj[key], null, languageKeyMap[toLanguage])
|
||||
targetObject[key] = translation
|
||||
translatedKeys.push(`${key}: ${translation}`)
|
||||
console.log(`✅ Retry successful: "${translation}"`)
|
||||
}
|
||||
catch (retryError) {
|
||||
console.error(`❌ Retry failed for key ${key}:`, retryError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = targetObject[key] || {}
|
||||
await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
const result = await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
skippedKeys.push(...result.skipped)
|
||||
translatedKeys.push(...result.translated)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async function autoGenTrans(fileName, toGenLanguage) {
|
||||
const fullKeyFilePath = path.join(__dirname, targetLanguage, `${fileName}.ts`)
|
||||
const toGenLanguageFilePath = path.join(__dirname, toGenLanguage, `${fileName}.ts`)
|
||||
// eslint-disable-next-line sonarjs/code-eval
|
||||
const fullKeyContent = eval(transpile(fs.readFileSync(fullKeyFilePath, 'utf8')))
|
||||
// if toGenLanguageFilePath is not exist, create it
|
||||
if (!fs.existsSync(toGenLanguageFilePath)) {
|
||||
fs.writeFileSync(toGenLanguageFilePath, `const translation = {
|
||||
return { skipped: skippedKeys, translated: translatedKeys }
|
||||
}
|
||||
async function autoGenTrans(fileName, toGenLanguage, isDryRun = false) {
|
||||
const fullKeyFilePath = path.resolve(__dirname, i18nFolder, targetLanguage, `${fileName}.ts`)
|
||||
const toGenLanguageFilePath = path.resolve(__dirname, i18nFolder, toGenLanguage, `${fileName}.ts`)
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(fullKeyFilePath, 'utf8')
|
||||
|
||||
// Create a safer module environment for vm
|
||||
const moduleExports = {}
|
||||
const context = {
|
||||
exports: moduleExports,
|
||||
module: { exports: moduleExports },
|
||||
require,
|
||||
console,
|
||||
__filename: fullKeyFilePath,
|
||||
__dirname: path.dirname(fullKeyFilePath),
|
||||
}
|
||||
|
||||
// Use vm.runInNewContext instead of eval for better security
|
||||
vm.runInNewContext(transpile(content), context)
|
||||
|
||||
const fullKeyContent = moduleExports.default || moduleExports
|
||||
|
||||
if (!fullKeyContent || typeof fullKeyContent !== 'object')
|
||||
throw new Error(`Failed to extract translation object from ${fullKeyFilePath}`)
|
||||
|
||||
// if toGenLanguageFilePath is not exist, create it
|
||||
if (!fs.existsSync(toGenLanguageFilePath)) {
|
||||
fs.writeFileSync(toGenLanguageFilePath, `const translation = {
|
||||
}
|
||||
|
||||
export default translation
|
||||
`)
|
||||
}
|
||||
// To keep object format and format it for magicast to work: const translation = { ... } => export default {...}
|
||||
const readContent = await loadFile(toGenLanguageFilePath)
|
||||
const { code: toGenContent } = generateCode(readContent)
|
||||
const mod = await parseModule(`export default ${toGenContent.replace('export default translation', '').replace('const translation = ', '')}`)
|
||||
const toGenOutPut = mod.exports.default
|
||||
}
|
||||
// To keep object format and format it for magicast to work: const translation = { ... } => export default {...}
|
||||
const readContent = await loadFile(toGenLanguageFilePath)
|
||||
const { code: toGenContent } = generateCode(readContent)
|
||||
const mod = await parseModule(`export default ${toGenContent.replace('export default translation', '').replace('const translation = ', '')}`)
|
||||
const toGenOutPut = mod.exports.default
|
||||
|
||||
await translateMissingKeyDeeply(fullKeyContent, toGenOutPut, toGenLanguage)
|
||||
const { code } = generateCode(mod)
|
||||
const res = `const translation =${code.replace('export default', '')}
|
||||
console.log(`\n🌍 Processing ${fileName} for ${toGenLanguage}...`)
|
||||
const result = await translateMissingKeyDeeply(fullKeyContent, toGenOutPut, toGenLanguage)
|
||||
|
||||
// Generate summary report
|
||||
console.log(`\n📊 Translation Summary for ${fileName} -> ${toGenLanguage}:`)
|
||||
console.log(` ✅ Translated: ${result.translated.length} keys`)
|
||||
console.log(` ⏭️ Skipped: ${result.skipped.length} keys`)
|
||||
|
||||
if (result.skipped.length > 0) {
|
||||
console.log(`\n⚠️ Skipped keys in ${fileName} (${toGenLanguage}):`)
|
||||
result.skipped.slice(0, 5).forEach(item => console.log(` - ${item}`))
|
||||
if (result.skipped.length > 5)
|
||||
console.log(` ... and ${result.skipped.length - 5} more`)
|
||||
}
|
||||
|
||||
const { code } = generateCode(mod)
|
||||
const res = `const translation =${code.replace('export default', '')}
|
||||
|
||||
export default translation
|
||||
`.replace(/,\n\n/g, ',\n').replace('};', '}')
|
||||
|
||||
fs.writeFileSync(toGenLanguageFilePath, res)
|
||||
if (!isDryRun) {
|
||||
fs.writeFileSync(toGenLanguageFilePath, res)
|
||||
console.log(`💾 Saved translations to ${toGenLanguageFilePath}`)
|
||||
}
|
||||
else {
|
||||
console.log(`🔍 [DRY RUN] Would save translations to ${toGenLanguageFilePath}`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error processing file ${fullKeyFilePath}:`, error.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Add command line argument support
|
||||
const isDryRun = process.argv.includes('--dry-run')
|
||||
const targetFile = process.argv.find(arg => arg.startsWith('--file='))?.split('=')[1]
|
||||
const targetLang = process.argv.find(arg => arg.startsWith('--lang='))?.split('=')[1]
|
||||
|
||||
// Rate limiting helper
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// const fileName = 'workflow'
|
||||
// Promise.all(Object.keys(languageKeyMap).map(async (toLanguage) => {
|
||||
// await autoGenTrans(fileName, toLanguage)
|
||||
// }))
|
||||
console.log('🚀 Starting auto-gen-i18n script...')
|
||||
console.log(`📋 Mode: ${isDryRun ? 'DRY RUN (no files will be modified)' : 'LIVE MODE'}`)
|
||||
|
||||
const files = fs
|
||||
.readdirSync(path.join(__dirname, targetLanguage))
|
||||
.map(file => file.replace(/\.ts/, ''))
|
||||
.readdirSync(path.resolve(__dirname, i18nFolder, targetLanguage))
|
||||
.filter(file => /\.ts$/.test(file)) // Only process .ts files
|
||||
.map(file => file.replace(/\.ts$/, ''))
|
||||
.filter(f => f !== 'app-debug') // ast parse error in app-debug
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
await Promise.all(Object.keys(languageKeyMap).map(async (language) => {
|
||||
// Filter by target file if specified
|
||||
const filesToProcess = targetFile ? files.filter(f => f === targetFile) : files
|
||||
const languagesToProcess = targetLang ? [targetLang] : Object.keys(languageKeyMap)
|
||||
|
||||
console.log(`📁 Files to process: ${filesToProcess.join(', ')}`)
|
||||
console.log(`🌍 Languages to process: ${languagesToProcess.join(', ')}`)
|
||||
|
||||
let totalTranslated = 0
|
||||
let totalSkipped = 0
|
||||
let totalErrors = 0
|
||||
|
||||
// Process files sequentially to avoid API rate limits
|
||||
for (const file of filesToProcess) {
|
||||
console.log(`\n📄 Processing file: ${file}`)
|
||||
|
||||
// Process languages with rate limiting
|
||||
for (const language of languagesToProcess) {
|
||||
try {
|
||||
await autoGenTrans(file, language)
|
||||
const result = await autoGenTrans(file, language, isDryRun)
|
||||
totalTranslated += result.translated.length
|
||||
totalSkipped += result.skipped.length
|
||||
|
||||
// Rate limiting: wait 500ms between language processing
|
||||
await delay(500)
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Error translating ${file} to ${language}`, e)
|
||||
console.error(`❌ Error translating ${file} to ${language}:`, e.message)
|
||||
totalErrors++
|
||||
}
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Final summary
|
||||
console.log('\n🎉 Auto-translation completed!')
|
||||
console.log('📊 Final Summary:')
|
||||
console.log(` ✅ Total keys translated: ${totalTranslated}`)
|
||||
console.log(` ⏭️ Total keys skipped: ${totalSkipped}`)
|
||||
console.log(` ❌ Total errors: ${totalErrors}`)
|
||||
|
||||
if (isDryRun)
|
||||
console.log('\n💡 This was a dry run. To actually translate, run without --dry-run flag.')
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const transpile = require('typescript').transpile
|
||||
|
||||
const targetLanguage = 'en-US'
|
||||
const data = require('./languages.json')
|
||||
const languages = data.languages.filter(language => language.supported).map(language => language.value)
|
||||
|
||||
async function getKeysFromLanuage(language) {
|
||||
async function getKeysFromLanguage(language) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const folderPath = path.join(__dirname, language)
|
||||
let allKeys = []
|
||||
const folderPath = path.resolve(__dirname, '../i18n', language)
|
||||
const allKeys = []
|
||||
fs.readdir(folderPath, (err, files) => {
|
||||
if (err) {
|
||||
console.error('Error reading folder:', err)
|
||||
|
|
@ -17,37 +18,61 @@ async function getKeysFromLanuage(language) {
|
|||
return
|
||||
}
|
||||
|
||||
files.forEach((file) => {
|
||||
// Filter only .ts and .js files
|
||||
const translationFiles = files.filter(file => /\.(ts|js)$/.test(file))
|
||||
|
||||
translationFiles.forEach((file) => {
|
||||
const filePath = path.join(folderPath, file)
|
||||
const fileName = file.replace(/\.[^/.]+$/, '') // Remove file extension
|
||||
const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) =>
|
||||
c.toUpperCase(),
|
||||
) // Convert to camel case
|
||||
// console.log(camelCaseFileName)
|
||||
const content = fs.readFileSync(filePath, 'utf8')
|
||||
// eslint-disable-next-line sonarjs/code-eval
|
||||
const translationObj = eval(transpile(content))
|
||||
// console.log(translation)
|
||||
if(!translationObj || typeof translationObj !== 'object') {
|
||||
console.error(`Error parsing file: ${filePath}`)
|
||||
reject(new Error(`Error parsing file: ${filePath}`))
|
||||
return
|
||||
}
|
||||
const keys = Object.keys(translationObj)
|
||||
const nestedKeys = []
|
||||
const iterateKeys = (obj, prefix = '') => {
|
||||
for (const key in obj) {
|
||||
const nestedKey = prefix ? `${prefix}.${key}` : key
|
||||
nestedKeys.push(nestedKey)
|
||||
if (typeof obj[key] === 'object')
|
||||
iterateKeys(obj[key], nestedKey)
|
||||
}
|
||||
}
|
||||
iterateKeys(translationObj)
|
||||
|
||||
allKeys = [...keys, ...nestedKeys].map(
|
||||
key => `${camelCaseFileName}.${key}`,
|
||||
)
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8')
|
||||
|
||||
// Create a safer module environment for vm
|
||||
const moduleExports = {}
|
||||
const context = {
|
||||
exports: moduleExports,
|
||||
module: { exports: moduleExports },
|
||||
require,
|
||||
console,
|
||||
__filename: filePath,
|
||||
__dirname: folderPath,
|
||||
}
|
||||
|
||||
// Use vm.runInNewContext instead of eval for better security
|
||||
vm.runInNewContext(transpile(content), context)
|
||||
|
||||
// Extract the translation object
|
||||
const translationObj = moduleExports.default || moduleExports
|
||||
|
||||
if(!translationObj || typeof translationObj !== 'object') {
|
||||
console.error(`Error parsing file: ${filePath}`)
|
||||
reject(new Error(`Error parsing file: ${filePath}`))
|
||||
return
|
||||
}
|
||||
|
||||
const nestedKeys = []
|
||||
const iterateKeys = (obj, prefix = '') => {
|
||||
for (const key in obj) {
|
||||
const nestedKey = prefix ? `${prefix}.${key}` : key
|
||||
nestedKeys.push(nestedKey)
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null)
|
||||
iterateKeys(obj[key], nestedKey)
|
||||
}
|
||||
}
|
||||
iterateKeys(translationObj)
|
||||
|
||||
// Fixed: accumulate keys instead of overwriting
|
||||
const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`)
|
||||
allKeys.push(...fileKeys)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error processing file ${filePath}:`, error.message)
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
resolve(allKeys)
|
||||
})
|
||||
|
|
@ -56,8 +81,8 @@ async function getKeysFromLanuage(language) {
|
|||
|
||||
async function main() {
|
||||
const compareKeysCount = async () => {
|
||||
const targetKeys = await getKeysFromLanuage(targetLanguage)
|
||||
const languagesKeys = await Promise.all(languages.map(language => getKeysFromLanuage(language)))
|
||||
const targetKeys = await getKeysFromLanguage(targetLanguage)
|
||||
const languagesKeys = await Promise.all(languages.map(language => getKeysFromLanguage(language)))
|
||||
|
||||
const keysCount = languagesKeys.map(keys => keys.length)
|
||||
const targetKeysCount = targetKeys.length
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Massenimport',
|
||||
bulkExport: 'Massenexport',
|
||||
clearAll: 'Alle Anmerkungen löschen',
|
||||
clearAllConfirm: 'Alle Anmerkungen löschen?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Datei-Upload',
|
||||
description: 'Das Chat-Eingabefeld unterstützt das Hochladen von Bildern, Dokumenten und anderen Dateien.',
|
||||
supportedTypes: 'Unterstützte Dateitypen',
|
||||
numberLimit: 'Max. Uploads',
|
||||
modalTitle: 'Datei-Upload-Einstellung',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Bild-Upload',
|
||||
description: 'Ermöglicht das Hochladen von Bildern.',
|
||||
supportedTypes: 'Unterstützte Dateitypen',
|
||||
numberLimit: 'Max. Uploads',
|
||||
modalTitle: 'Bild-Upload-Einstellung',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Funktionen aktivieren, um die Web-App-Benutzererfahrung zu verbessern',
|
||||
enableText: 'Funktionen aktiviert',
|
||||
manage: 'Verwalten',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Dokument',
|
||||
description: 'Das Aktivieren von Dokumenten ermöglicht es dem Modell, Dokumente aufzunehmen und Fragen zu ihnen zu beantworten.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Das Aktivieren von Audio ermöglicht es dem Modell, Audiodateien für Transkription und Analyse zu verarbeiten.',
|
||||
},
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'Zurücksetzen bestätigen?',
|
||||
|
|
|
|||
|
|
@ -268,6 +268,7 @@ const translation = {
|
|||
noAccessPermission: 'Keine Berechtigung zum Zugriff auf die Webanwendung',
|
||||
maxActiveRequests: 'Maximale gleichzeitige Anfragen',
|
||||
maxActiveRequestsPlaceholder: 'Geben Sie 0 für unbegrenzt ein',
|
||||
maxActiveRequestsTip: 'Maximale Anzahl gleichzeitiger aktiver Anfragen pro App (0 für unbegrenzt)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'Nur fixieren',
|
||||
selectedDescription: 'Auto-Update nur für Patch-Versionen',
|
||||
description: 'Automatische Aktualisierung nur für Patchversionen (z. B. 1.0.1 → 1.0.2). Kleinere Versionsänderungen lösen keine Aktualisierungen aus.',
|
||||
},
|
||||
latest: {
|
||||
description: 'Immer auf die neueste Version aktualisieren',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
search: 'Suchmetadaten',
|
||||
},
|
||||
title: 'Metadatenfilterung',
|
||||
tip: 'Metadatenfilterung ist der Prozess, Metadatenattribute (wie Tags, Kategorien oder Zugriffsberechtigungen) zu verwenden, um die Abfrage und Kontrolle der relevanten Informationen innerhalb eines Systems zu verfeinern.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Importar en Masa',
|
||||
bulkExport: 'Exportar en Masa',
|
||||
clearAll: 'Borrar Todas las Anotaciones',
|
||||
clearAllConfirm: '¿Eliminar todas las anotaciones?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Subida de archivos',
|
||||
description: 'La caja de entrada del chat permite subir imágenes, documentos y otros archivos.',
|
||||
supportedTypes: 'Tipos de archivo soportados',
|
||||
numberLimit: 'Máximo de subidas',
|
||||
modalTitle: 'Configuración de subida de archivos',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Subida de imágenes',
|
||||
description: 'Permite subir imágenes.',
|
||||
supportedTypes: 'Tipos de archivo soportados',
|
||||
numberLimit: 'Máximo de subidas',
|
||||
modalTitle: 'Configuración de subida de imágenes',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Habilitar funciones para mejorar la experiencia del usuario de la aplicación web',
|
||||
enableText: 'Funciones habilitadas',
|
||||
manage: 'Gestionar',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Documento',
|
||||
description: 'Habilitar Documento permitirá al modelo aceptar documentos y responder preguntas sobre ellos.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Habilitar Audio permitirá al modelo procesar archivos de audio para transcripción y análisis.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Orquestación automatizada de aplicaciones',
|
||||
|
|
@ -282,7 +309,6 @@ const translation = {
|
|||
'required': 'Requerido',
|
||||
'hide': 'Ocultar',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'Nombre de la variable es requerido',
|
||||
labelNameRequired: 'Nombre de la etiqueta es requerido',
|
||||
varNameCanBeRepeat: 'El nombre de la variable no puede repetirse',
|
||||
atLeastOneOption: 'Se requiere al menos una opción',
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'No se permite el acceso a la aplicación web',
|
||||
maxActiveRequestsPlaceholder: 'Introduce 0 para ilimitado',
|
||||
maxActiveRequests: 'Máximas solicitudes concurrentes',
|
||||
maxActiveRequestsTip: 'Número máximo de solicitudes activas concurrentes por aplicación (0 para ilimitado)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'Arreglar Solo',
|
||||
selectedDescription: 'Actualización automática solo para versiones de parches',
|
||||
description: 'Actualización automática solo para versiones de parche (por ejemplo, 1.0.1 → 1.0.2). Los cambios de versión menor no activarán actualizaciones.',
|
||||
},
|
||||
latest: {
|
||||
selectedDescription: 'Siempre actualiza a la última versión',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
search: 'Buscar metadatos',
|
||||
},
|
||||
title: 'Filtrado de Metadatos',
|
||||
tip: 'El filtrado de metadatos es el proceso de utilizar atributos de metadatos (como etiquetas, categorías o permisos de acceso) para refinar y controlar la recuperación de información relevante dentro de un sistema.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'واردات انبوه',
|
||||
bulkExport: 'صادرات انبوه',
|
||||
clearAll: 'پاک کردن همه یادداشتها',
|
||||
clearAllConfirm: 'آیا همه حاشیهنویسیها را حذف کنیم؟',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -317,7 +317,6 @@ const translation = {
|
|||
'required': 'مورد نیاز',
|
||||
'hide': 'مخفی کردن',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'نام متغیر مورد نیاز است',
|
||||
labelNameRequired: 'نام برچسب مورد نیاز است',
|
||||
varNameCanBeRepeat: 'نام متغیر نمیتواند تکراری باشد',
|
||||
atLeastOneOption: 'حداقل یک گزینه مورد نیاز است',
|
||||
|
|
@ -451,6 +450,33 @@ const translation = {
|
|||
enabled: 'فعال',
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'آپلود فایل',
|
||||
description: 'جعبه ورودی چت امکان آپلود تصاویر، اسناد و سایر فایلها را فراهم میکند.',
|
||||
supportedTypes: 'انواع فایلهای پشتیبانی شده',
|
||||
numberLimit: 'حداکثر آپلود',
|
||||
modalTitle: 'تنظیمات آپلود فایل',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'آپلود تصویر',
|
||||
description: 'امکان آپلود تصاویر را فراهم میکند.',
|
||||
supportedTypes: 'انواع فایلهای پشتیبانی شده',
|
||||
numberLimit: 'حداکثر آپلود',
|
||||
modalTitle: 'تنظیمات آپلود تصویر',
|
||||
},
|
||||
bar: {
|
||||
empty: 'فعالسازی ویژگی برای بهبود تجربه کاربری اپلیکیشن وب',
|
||||
enableText: 'ویژگیهای فعال',
|
||||
manage: 'مدیریت',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'سند',
|
||||
description: 'فعالسازی سند به مدل اجازه میدهد اسناد را دریافت کرده و درباره آنها پاسخ دهد.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'صوتی',
|
||||
description: 'فعالسازی صوت به مدل اجازه میدهد فایلهای صوتی را برای رونویسی و تجزیه و تحلیل پردازش کند.',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'دسترسی به برنامه وب مجاز نیست',
|
||||
maxActiveRequests: 'بیشترین درخواستهای همزمان',
|
||||
maxActiveRequestsPlaceholder: 'برای نامحدود، 0 را وارد کنید',
|
||||
maxActiveRequestsTip: 'حداکثر تعداد درخواستهای فعال همزمان در هر برنامه (0 برای نامحدود)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'فقط تعمیر کنید',
|
||||
selectedDescription: 'بهروزرسانی خودکار تنها برای نسخههای وصله',
|
||||
description: 'بهروزرسانی خودکار فقط برای نسخههای پچ (مانند ۱.۰.۱ → ۱.۰.۲). تغییرات جزئی نسخه باعث راهاندازی بهروزرسانیها نمیشود.',
|
||||
},
|
||||
latest: {
|
||||
name: 'جدیدترین',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
conditions: 'شرایط',
|
||||
},
|
||||
title: 'فیلتر کردن فراداده',
|
||||
tip: 'فیلتر کردن متاداده فرایند استفاده از ویژگیهای متاداده (مانند برچسبها، دستهها یا مجوزهای دسترسی) برای تصفیه و کنترل بازیابی اطلاعات مرتبط در یک سیستم است.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Importation en Vrac',
|
||||
bulkExport: 'Exportation en Vrac',
|
||||
clearAll: 'Effacer toutes les annotations',
|
||||
clearAllConfirm: 'Supprimer toutes les annotations ?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Téléchargement de fichier',
|
||||
description: 'La boîte de saisie de chat permet de télécharger des images, des documents et d\'autres fichiers.',
|
||||
supportedTypes: 'Types de fichiers supportés',
|
||||
numberLimit: 'Nombre max de téléchargements',
|
||||
modalTitle: 'Paramètres de téléchargement de fichier',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Téléchargement d\'image',
|
||||
description: 'Permet de télécharger des images.',
|
||||
supportedTypes: 'Types de fichiers supportés',
|
||||
numberLimit: 'Nombre max de téléchargements',
|
||||
modalTitle: 'Paramètres de téléchargement d\'image',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Activer la fonctionnalité pour améliorer l\'expérience utilisateur de l\'application web',
|
||||
enableText: 'Fonctionnalités activées',
|
||||
manage: 'Gérer',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Document',
|
||||
description: 'Activer Document permettra au modèle de prendre des documents et de répondre aux questions à leur sujet.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Activer Audio permettra au modèle de traiter les fichiers audio pour la transcription et l\'analyse.',
|
||||
},
|
||||
},
|
||||
resetConfig: {
|
||||
title: 'Confirmer la réinitialisation ?',
|
||||
|
|
@ -270,7 +297,6 @@ const translation = {
|
|||
'required': 'Required',
|
||||
'hide': 'Caché',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'Variable name is required',
|
||||
labelNameRequired: 'Label name is required',
|
||||
varNameCanBeRepeat: 'Variable name can not be repeated',
|
||||
atLeastOneOption: 'At least one option is required',
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'Pas de permission d\'accéder à l\'application web',
|
||||
maxActiveRequestsPlaceholder: 'Entrez 0 pour illimité',
|
||||
maxActiveRequests: 'Nombre maximal de requêtes simultanées',
|
||||
maxActiveRequestsTip: 'Nombre maximum de requêtes actives concurrentes par application (0 pour illimité)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
selectedDescription: 'Mise à jour automatique uniquement pour les versions de correctif',
|
||||
name: 'Réparer seulement',
|
||||
description: 'Mise à jour automatique uniquement pour les versions de correctif (par exemple, 1.0.1 → 1.0.2). Les changements de version mineure ne déclencheront pas de mises à jour.',
|
||||
},
|
||||
latest: {
|
||||
name: 'Dernier',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
title: 'Conditions de filtrage des métadonnées',
|
||||
},
|
||||
title: 'Filtrage des métadonnées',
|
||||
tip: 'Le filtrage des métadonnées est le processus d\'utilisation des attributs de métadonnées (tels que les étiquettes, les catégories ou les autorisations d\'accès) pour affiner et contrôler la récupération d\'informations pertinentes au sein d\'un système.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'बल्क आयात',
|
||||
bulkExport: 'बल्क निर्यात',
|
||||
clearAll: 'सभी एनोटेशन साफ करें',
|
||||
clearAllConfirm: 'क्या सभी टिप्पणियाँ हटानी हैं?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -314,7 +314,6 @@ const translation = {
|
|||
'required': 'आवश्यक',
|
||||
'hide': 'छुपाएँ',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'वेरिएबल नाम आवश्यक है',
|
||||
labelNameRequired: 'लेबल नाम आवश्यक है',
|
||||
varNameCanBeRepeat: 'वेरिएबल नाम दोहराया नहीं जा सकता',
|
||||
atLeastOneOption: 'कम से कम एक विकल्प आवश्यक है',
|
||||
|
|
@ -468,6 +467,33 @@ const translation = {
|
|||
'उपकरणों का उपयोग करके एलएलएम की क्षमताओं का विस्तार किया जा सकता है, जैसे इंटरनेट पर खोज करना या वैज्ञानिक गणनाएँ करना',
|
||||
enabled: 'सक्षम',
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'फ़ाइल अपलोड',
|
||||
description: 'चैट इनपुट बॉक्स छवियों, दस्तावेज़ों और अन्य फ़ाइलों को अपलोड करने की अनुमति देता है।',
|
||||
supportedTypes: 'समर्थित फ़ाइल प्रकार',
|
||||
numberLimit: 'अधिकतम अपलोड',
|
||||
modalTitle: 'फ़ाइल अपलोड सेटिंग',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'छवि अपलोड',
|
||||
description: 'छवियों को अपलोड करने की अनुमति दें।',
|
||||
supportedTypes: 'समर्थित फ़ाइल प्रकार',
|
||||
numberLimit: 'अधिकतम अपलोड',
|
||||
modalTitle: 'छवि अपलोड सेटिंग',
|
||||
},
|
||||
bar: {
|
||||
empty: 'वेब ऐप उपयोगकर्ता अनुभव को बेहतर बनाने के लिए फीचर सक्षम करें',
|
||||
enableText: 'फीचर सक्षम',
|
||||
manage: 'प्रबंधित करें',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'दस्तावेज़',
|
||||
description: 'दस्तावेज़ सक्षम करने से मॉडल दस्तावेज़ों को स्वीकार कर सकेगा और उनके बारे में प्रश्नों का उत्तर दे सकेगा।',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'ऑडियो',
|
||||
description: 'ऑडियो सक्षम करने से मॉडल ट्रांसक्रिप्शन और विश्लेषण के लिए ऑडियो फ़ाइलों को प्रोसेस कर सकेगा।',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'वेब एप्लिकेशन तक पहुँचने की अनुमति नहीं है',
|
||||
maxActiveRequests: 'अधिकतम समवर्ती अनुरोध',
|
||||
maxActiveRequestsPlaceholder: 'असीमित के लिए 0 दर्ज करें',
|
||||
maxActiveRequestsTip: 'प्रति ऐप अधिकतम सक्रिय अनुरोधों की अधिकतम संख्या (असीमित के लिए 0)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'केवल ठीक करें',
|
||||
selectedDescription: 'केवल पैच संस्करणों के लिए स्वचालित अपडेट',
|
||||
description: 'केवल पैच संस्करणों के लिए स्वचालित अद्यतन (जैसे, 1.0.1 → 1.0.2)। छोटा संस्करण परिवर्तन अद्यतन को ट्रिगर नहीं करेगा।',
|
||||
},
|
||||
latest: {
|
||||
name: 'नवीनतम',
|
||||
|
|
|
|||
|
|
@ -510,6 +510,7 @@ const translation = {
|
|||
search: 'खोज मेटाडेटा',
|
||||
},
|
||||
title: 'मेटाडेटा फ़िल्टरिंग',
|
||||
tip: 'मेटाडेटा छानने की प्रक्रिया है जिसमें मेटाडेटा विशेषताओं (जैसे टैग, श्रेणियाँ, या पहुंच अनुमतियाँ) का उपयोग करके एक प्रणाली के भीतर प्रासंगिक जानकारी की पुनर्प्राप्ति को सुधारने और नियंत्रित करने के लिए किया जाता है।',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const translation = {
|
|||
bulkImport: 'Importazione Bulk',
|
||||
bulkExport: 'Esportazione Bulk',
|
||||
clearAll: 'Cancella Tutte le Annotazioni',
|
||||
clearAllConfirm: 'Eliminare tutte le annotazioni?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -216,6 +216,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Caricamento File',
|
||||
description: 'La casella di input della chat consente di caricare immagini, documenti e altri file.',
|
||||
supportedTypes: 'Tipi di File Supportati',
|
||||
numberLimit: 'Caricamenti massimi',
|
||||
modalTitle: 'Impostazione Caricamento File',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Caricamento Immagine',
|
||||
description: 'Consente di caricare immagini.',
|
||||
supportedTypes: 'Tipi di File Supportati',
|
||||
numberLimit: 'Caricamenti massimi',
|
||||
modalTitle: 'Impostazione Caricamento Immagine',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Abilita funzionalità per migliorare l\'esperienza utente dell\'app web',
|
||||
enableText: 'Funzionalità Abilitate',
|
||||
manage: 'Gestisci',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Documento',
|
||||
description: 'Abilitare Documento consentirà al modello di accettare documenti e rispondere a domande su di essi.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Abilitare Audio consentirà al modello di elaborare file audio per trascrizione e analisi.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Orchestrazione automatizzata delle applicazioni',
|
||||
|
|
@ -316,7 +343,6 @@ const translation = {
|
|||
'required': 'Richiesto',
|
||||
'hide': 'Nascondi',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'Il nome della variabile è richiesto',
|
||||
labelNameRequired: 'Il nome dell\'etichetta è richiesto',
|
||||
varNameCanBeRepeat: 'Il nome della variabile non può essere ripetuto',
|
||||
atLeastOneOption: 'È richiesta almeno un\'opzione',
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ const translation = {
|
|||
noAccessPermission: 'Nessun permesso per accedere all\'app web',
|
||||
maxActiveRequestsPlaceholder: 'Inserisci 0 per illimitato',
|
||||
maxActiveRequests: 'Massimo numero di richieste concorrenti',
|
||||
maxActiveRequestsTip: 'Numero massimo di richieste attive concorrenti per app (0 per illimitato)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'Ripara solo',
|
||||
selectedDescription: 'Aggiornamento automatico solo per versioni patch',
|
||||
description: 'Aggiornamento automatico solo per le versioni patch (ad es., 1.0.1 → 1.0.2). Le modifiche delle versioni minori non attiveranno aggiornamenti.',
|
||||
},
|
||||
latest: {
|
||||
selectedDescription: 'Aggiorna sempre all\'ultima versione',
|
||||
|
|
|
|||
|
|
@ -514,6 +514,7 @@ const translation = {
|
|||
search: 'Cerca metadati',
|
||||
},
|
||||
title: 'Filtraggio dei metadati',
|
||||
tip: 'Il filtraggio dei metadati è il processo di utilizzo degli attributi dei metadati (come tag, categorie o permessi di accesso) per affinare e controllare il recupero di informazioni pertinenti all\'interno di un sistema.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ const translation = {
|
|||
title: 'ドキュメント',
|
||||
description: 'ドキュメント機能を有効にすると、AI モデルがファイルを処理し、その内容に基づいて質問に回答できるようになります。',
|
||||
},
|
||||
audioUpload: {
|
||||
title: '音声',
|
||||
description: '音声機能を有効にすると、モデルが音声ファイルの転写と分析を処理できるようになります。',
|
||||
},
|
||||
},
|
||||
codegen: {
|
||||
title: 'コードジェネレーター',
|
||||
|
|
@ -307,6 +311,9 @@ const translation = {
|
|||
waitForImgUpload: '画像のアップロードが完了するまでお待ちください',
|
||||
waitForFileUpload: 'ファイルのアップロードが完了するまでお待ちください',
|
||||
},
|
||||
warningMessage: {
|
||||
timeoutExceeded: 'タイムアウトのため結果が表示されません。完全な結果を取得するにはログを参照してください。',
|
||||
},
|
||||
chatSubTitle: 'プロンプト',
|
||||
completionSubTitle: '接頭辞プロンプト',
|
||||
promptTip: 'プロンプトは、AI の応答を指示と制約で誘導します。 {{input}} のような変数を挿入します。このプロンプトはユーザーには表示されません。',
|
||||
|
|
@ -386,7 +393,6 @@ const translation = {
|
|||
'maxNumberOfUploads': 'アップロードの最大数',
|
||||
'maxNumberTip': 'ドキュメント < {{docLimit}}, 画像 < {{imgLimit}}, 音声 < {{audioLimit}}, 映像 < {{videoLimit}}',
|
||||
'errorMsg': {
|
||||
varNameRequired: '変数名は必須です',
|
||||
labelNameRequired: 'ラベル名は必須です',
|
||||
varNameCanBeRepeat: '変数名は繰り返すことができません',
|
||||
atLeastOneOption: '少なくとも 1 つのオプションが必要です',
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ const translation = {
|
|||
noAccessPermission: 'Web アプリにアクセス権限がありません',
|
||||
maxActiveRequestsPlaceholder: '無制限のために0を入力してください',
|
||||
maxActiveRequests: '最大同時リクエスト数',
|
||||
maxActiveRequestsTip: 'アプリごとの同時アクティブリクエストの最大数(無制限の場合は0)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: '修正のみ',
|
||||
selectedDescription: 'パッチバージョンのみの自動更新',
|
||||
description: 'パッチバージョンのみ自動更新 (例: 1.0.1 → 1.0.2)。マイナーバージョンの変更は更新をトリガーしません。',
|
||||
},
|
||||
latest: {
|
||||
name: '最新',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: '일괄 가져오기',
|
||||
bulkExport: '일괄 내보내기',
|
||||
clearAll: '모든 어노테이션 지우기',
|
||||
clearAllConfirm: '모든 주석을 삭제하시겠습니까?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: '파일 업로드',
|
||||
description: '채팅 입력 상자에서 이미지, 문서 및 기타 파일 업로드를 지원합니다.',
|
||||
supportedTypes: '지원 파일 유형',
|
||||
numberLimit: '최대 업로드 수',
|
||||
modalTitle: '파일 업로드 설정',
|
||||
},
|
||||
imageUpload: {
|
||||
title: '이미지 업로드',
|
||||
description: '이미지 업로드를 지원합니다.',
|
||||
supportedTypes: '지원 파일 유형',
|
||||
numberLimit: '최대 업로드 수',
|
||||
modalTitle: '이미지 업로드 설정',
|
||||
},
|
||||
bar: {
|
||||
empty: '웹 앱 사용자 경험을 향상시키는 기능 활성화',
|
||||
enableText: '기능 활성화됨',
|
||||
manage: '관리',
|
||||
},
|
||||
documentUpload: {
|
||||
title: '문서',
|
||||
description: '문서를 활성화하면 모델이 문서를 받아들이고 문서에 대한 질문에 답할 수 있습니다.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: '오디오',
|
||||
description: '오디오를 활성화하면 모델이 전사 및 분석을 위해 오디오 파일을 처리할 수 있습니다.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: '자동 어플리케이션 오케스트레이션',
|
||||
|
|
@ -281,7 +308,6 @@ const translation = {
|
|||
'required': '필수',
|
||||
'hide': '숨기기',
|
||||
'errorMsg': {
|
||||
varNameRequired: '변수명은 필수입니다',
|
||||
labelNameRequired: '레이블명은 필수입니다',
|
||||
varNameCanBeRepeat: '변수명은 중복될 수 없습니다',
|
||||
atLeastOneOption: '적어도 하나의 옵션이 필요합니다',
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ const translation = {
|
|||
noAccessPermission: '웹 앱에 대한 접근 권한이 없습니다.',
|
||||
maxActiveRequests: '동시 최대 요청 수',
|
||||
maxActiveRequestsPlaceholder: '무제한 사용을 원하시면 0을 입력하세요.',
|
||||
maxActiveRequestsTip: '앱당 최대 동시 활성 요청 수(무제한은 0)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: '수정만 하기',
|
||||
selectedDescription: '패치 버전만 자동 업데이트',
|
||||
description: '패치 버전만 자동 업데이트 (예: 1.0.1 → 1.0.2). 마이너 버전 변경은 업데이트를 유발하지 않습니다.',
|
||||
},
|
||||
latest: {
|
||||
name: '최신',
|
||||
|
|
|
|||
|
|
@ -525,6 +525,7 @@ const translation = {
|
|||
conditions: '조건',
|
||||
},
|
||||
title: '메타데이터 필터링',
|
||||
tip: '메타데이터 필터링은 시스템 내에서 관련 정보를 검색하는 과정을 정제하고 제어하기 위해 메타데이터 속성(예: 태그, 카테고리 또는 접근 권한)을 사용하는 과정입니다.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const translation = {
|
|||
bulkImport: 'Masowy import',
|
||||
bulkExport: 'Masowy eksport',
|
||||
clearAll: 'Wyczyść wszystkie adnotacje',
|
||||
clearAllConfirm: 'Usunąć wszystkie adnotacje?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -214,6 +214,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Przesyłanie plików',
|
||||
description: 'Pole wprowadzania czatu umożliwia przesyłanie obrazów, dokumentów i innych plików.',
|
||||
supportedTypes: 'Obsługiwane typy plików',
|
||||
numberLimit: 'Maksymalna liczba przesłanych plików',
|
||||
modalTitle: 'Ustawienia przesyłania plików',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Przesyłanie obrazów',
|
||||
description: 'Umożliwia przesyłanie obrazów.',
|
||||
supportedTypes: 'Obsługiwane typy plików',
|
||||
numberLimit: 'Maksymalna liczba przesłanych plików',
|
||||
modalTitle: 'Ustawienia przesyłania obrazów',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Włącz funkcje aby poprawić doświadczenie użytkownika aplikacji webowej',
|
||||
enableText: 'Funkcje włączone',
|
||||
manage: 'Zarządzaj',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Dokument',
|
||||
description: 'Włączenie Dokumentu pozwoli modelowi na przyjmowanie dokumentów i odpowiadanie na pytania o nich.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Dźwięk',
|
||||
description: 'Włączenie Dźwięku pozwoli modelowi na przetwarzanie plików audio do transkrypcji i analizy.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Zautomatyzowana orkiestracja aplikacji',
|
||||
|
|
@ -311,7 +338,6 @@ const translation = {
|
|||
'required': 'Wymagane',
|
||||
'hide': 'Ukryj',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'Wymagana nazwa zmiennej',
|
||||
labelNameRequired: 'Wymagana nazwa etykiety',
|
||||
varNameCanBeRepeat: 'Nazwa zmiennej nie może się powtarzać',
|
||||
atLeastOneOption: 'Wymagana jest co najmniej jedna opcja',
|
||||
|
|
|
|||
|
|
@ -268,6 +268,7 @@ const translation = {
|
|||
noAccessPermission: 'Brak uprawnień do dostępu do aplikacji internetowej',
|
||||
maxActiveRequests: 'Maksymalne równoczesne żądania',
|
||||
maxActiveRequestsPlaceholder: 'Wprowadź 0, aby uzyskać nielimitowane',
|
||||
maxActiveRequestsTip: 'Maksymalna liczba jednoczesnych aktywnych żądań na aplikację (0 dla nieograniczonej)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
selectedDescription: 'Automatyczna aktualizacja tylko dla wersji poprawek',
|
||||
name: 'Napraw tylko',
|
||||
description: 'Automatyczna aktualizacja tylko dla wersji łatkowych (np. 1.0.1 → 1.0.2). Zmiany w wersjach mniejszych nie będą wywoływać aktualizacji.',
|
||||
},
|
||||
latest: {
|
||||
name: 'Najświeższy',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
select: 'Wybierz zmienną...',
|
||||
},
|
||||
title: 'Filtrowanie metadanych',
|
||||
tip: 'Filtracja metadanych to proces wykorzystania atrybutów metadanych (takich jak tagi, kategorie lub uprawnienia dostępu) do precyzowania i kontrolowania pozyskiwania istotnych informacji w systemie.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Importação em Massa',
|
||||
bulkExport: 'Exportação em Massa',
|
||||
clearAll: 'Limpar Todas as Anotações',
|
||||
clearAllConfirm: 'Excluir todas as anotações?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Upload de Arquivo',
|
||||
description: 'A caixa de entrada do chat permite fazer upload de imagens, documentos e outros arquivos.',
|
||||
supportedTypes: 'Tipos de Arquivo Suportados',
|
||||
numberLimit: 'Máximo de uploads',
|
||||
modalTitle: 'Configuração de Upload de Arquivo',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Upload de Imagem',
|
||||
description: 'Permite fazer upload de imagens.',
|
||||
supportedTypes: 'Tipos de Arquivo Suportados',
|
||||
numberLimit: 'Máximo de uploads',
|
||||
modalTitle: 'Configuração de Upload de Imagem',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Habilitar recursos para melhorar a experiência do usuário do aplicativo web',
|
||||
enableText: 'Recursos Habilitados',
|
||||
manage: 'Gerenciar',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Documento',
|
||||
description: 'Habilitar Documento permitirá que o modelo aceite documentos e responda perguntas sobre eles.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Áudio',
|
||||
description: 'Habilitar Áudio permitirá que o modelo processe arquivos de áudio para transcrição e análise.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Orquestração Automatizada de Aplicativos',
|
||||
|
|
@ -287,7 +314,6 @@ const translation = {
|
|||
'required': 'Obrigatório',
|
||||
'hide': 'Ocultar',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'O nome da variável é obrigatório',
|
||||
labelNameRequired: 'O nome do rótulo é obrigatório',
|
||||
varNameCanBeRepeat: 'O nome da variável não pode ser repetido',
|
||||
atLeastOneOption: 'Pelo menos uma opção é obrigatória',
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'Sem permissão para acessar o aplicativo web',
|
||||
maxActiveRequestsPlaceholder: 'Digite 0 para ilimitado',
|
||||
maxActiveRequests: 'Máximo de solicitações simultâneas',
|
||||
maxActiveRequestsTip: 'Número máximo de solicitações ativas simultâneas por aplicativo (0 para ilimitado)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
selectedDescription: 'Atualização automática apenas para versões de patch',
|
||||
name: 'Reparar Apenas',
|
||||
description: 'Atualização automática apenas para versões de patch (por exemplo, 1.0.1 → 1.0.2). Mudanças de versão menor não ativarão atualizações.',
|
||||
},
|
||||
latest: {
|
||||
description: 'Sempre atualize para a versão mais recente',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
placeholder: 'Insira o valor',
|
||||
},
|
||||
title: 'Filtragem de Metadados',
|
||||
tip: 'A filtragem de metadados é o processo de usar atributos de metadados (como etiquetas, categorias ou permissões de acesso) para refinar e controlar a recuperação de informações relevantes dentro de um sistema.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Import în Masă',
|
||||
bulkExport: 'Export în Masă',
|
||||
clearAll: 'Șterge Toate Anotațiile',
|
||||
clearAllConfirm: 'Șterge toate anotările?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Încărcare fișier',
|
||||
description: 'Caseta de intrare chat permite încărcarea de imagini, documente și alte fișiere.',
|
||||
supportedTypes: 'Tipuri de fișiere suportate',
|
||||
numberLimit: 'Numărul maxim de încărcări',
|
||||
modalTitle: 'Setări încărcare fișier',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Încărcare imagine',
|
||||
description: 'Permite încărcarea imaginilor.',
|
||||
supportedTypes: 'Tipuri de fișiere suportate',
|
||||
numberLimit: 'Numărul maxim de încărcări',
|
||||
modalTitle: 'Setări încărcare imagine',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Activează funcții pentru a îmbunătăți experiența utilizatorilor aplicației web',
|
||||
enableText: 'Funcții activate',
|
||||
manage: 'Gestionează',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Document',
|
||||
description: 'Activarea Documentului va permite modelului să primească documente și să răspundă la întrebări despre ele.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Audio',
|
||||
description: 'Activarea Audio va permite modelului să proceseze fișiere audio pentru transcriere și analiză.',
|
||||
},
|
||||
},
|
||||
automatic: {
|
||||
title: 'Orchestrarea automată a aplicațiilor',
|
||||
|
|
@ -287,7 +314,6 @@ const translation = {
|
|||
'required': 'Obligatoriu',
|
||||
'hide': 'Ascundeți',
|
||||
'errorMsg': {
|
||||
varNameRequired: 'Numele variabilei este obligatoriu',
|
||||
labelNameRequired: 'Numele etichetei este obligatoriu',
|
||||
varNameCanBeRepeat: 'Numele variabilei nu poate fi repetat',
|
||||
atLeastOneOption: 'Este necesară cel puțin o opțiune',
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'Nici o permisiune pentru a accesa aplicația web',
|
||||
maxActiveRequestsPlaceholder: 'Introduceți 0 pentru nelimitat',
|
||||
maxActiveRequests: 'Maxime cereri simultane',
|
||||
maxActiveRequestsTip: 'Numărul maxim de cereri active concurente pe aplicație (0 pentru nelimitat)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
selectedDescription: 'Actualizare automată doar pentru versiuni patch',
|
||||
name: 'Fix doar',
|
||||
description: 'Actualizare automată doar pentru versiunile de patch (de exemplu, 1.0.1 → 1.0.2). Schimbările de versiune minore nu vor declanșa actualizări.',
|
||||
},
|
||||
latest: {
|
||||
name: 'Ultimul',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
search: 'Căutare metadate',
|
||||
},
|
||||
title: 'Filtrarea metadatelor',
|
||||
tip: 'Filtrarea metadatelor este procesul de utilizare a atributelor metadatelor (cum ar fi etichetele, categoriile sau permisiunile de acces) pentru a rafina și controla recuperarea informațiilor relevante într-un sistem.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Массовый импорт',
|
||||
bulkExport: 'Массовый экспорт',
|
||||
clearAll: 'Очистить все аннотации',
|
||||
clearAllConfirm: 'Удалить все аннотации?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,33 @@ const translation = {
|
|||
},
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
title: 'Загрузка файлов',
|
||||
description: 'Поле ввода чата позволяет загружать изображения, документы и другие файлы.',
|
||||
supportedTypes: 'Поддерживаемые типы файлов',
|
||||
numberLimit: 'Максимум загрузок',
|
||||
modalTitle: 'Настройка загрузки файлов',
|
||||
},
|
||||
imageUpload: {
|
||||
title: 'Загрузка изображений',
|
||||
description: 'Позволяет загружать изображения.',
|
||||
supportedTypes: 'Поддерживаемые типы файлов',
|
||||
numberLimit: 'Максимум загрузок',
|
||||
modalTitle: 'Настройка загрузки изображений',
|
||||
},
|
||||
bar: {
|
||||
empty: 'Включить функции для улучшения пользовательского опыта веб-приложения',
|
||||
enableText: 'Функции включены',
|
||||
manage: 'Управлять',
|
||||
},
|
||||
documentUpload: {
|
||||
title: 'Документ',
|
||||
description: 'Включение Документа позволит модели принимать документы и отвечать на вопросы о них.',
|
||||
},
|
||||
audioUpload: {
|
||||
title: 'Аудио',
|
||||
description: 'Включение Аудио позволит модели обрабатывать аудиофайлы для транскрипции и анализа.',
|
||||
},
|
||||
},
|
||||
generate: {
|
||||
title: 'Генератор промпта',
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ const translation = {
|
|||
noAccessPermission: 'Нет разрешения на доступ к веб-приложению',
|
||||
maxActiveRequests: 'Максимальное количество параллельных запросов',
|
||||
maxActiveRequestsPlaceholder: 'Введите 0 для неограниченного количества',
|
||||
maxActiveRequestsTip: 'Максимальное количество одновременно активных запросов на одно приложение (0 для неограниченного количества)',
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ const translation = {
|
|||
fixOnly: {
|
||||
name: 'Только исправить',
|
||||
selectedDescription: 'Автообновление только для версий патчей',
|
||||
description: 'Автообновление только для патч-версий (например, 1.0.1 → 1.0.2). Изменения в минорных версиях не вызовут обновления.',
|
||||
},
|
||||
latest: {
|
||||
name: 'Новости',
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const translation = {
|
|||
search: 'Поиск метаданных',
|
||||
},
|
||||
title: 'Фильтрация метаданных',
|
||||
tip: 'Фильтрация метаданных — это процесс использования атрибутов метаданных (таких как теги, категории или права доступа) для уточнения и контроля извлечения соответствующей информации внутри системы.',
|
||||
},
|
||||
},
|
||||
http: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const translation = {
|
|||
bulkImport: 'Množični uvoz',
|
||||
bulkExport: 'Množični izvoz',
|
||||
clearAll: 'Počisti vse opombe',
|
||||
clearAllConfirm: 'Izbrišite vse opombe?',
|
||||
},
|
||||
},
|
||||
editModal: {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue