mirror of
https://github.com/langgenius/dify.git
synced 2026-04-07 12:00:38 -04:00
Merge branch 'main' into jzh
This commit is contained in:
@@ -2,6 +2,7 @@ import csv
|
||||
import io
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import cast
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
@@ -17,7 +18,7 @@ from core.db.session_factory import session_factory
|
||||
from extensions.ext_database import db
|
||||
from libs.token import extract_access_token
|
||||
from models.model import App, ExporleBanner, InstalledApp, RecommendedApp, TrialApp
|
||||
from services.billing_service import BillingService
|
||||
from services.billing_service import BillingService, LangContentDict
|
||||
|
||||
DEFAULT_REF_TEMPLATE_SWAGGER_2_0 = "#/definitions/{model}"
|
||||
|
||||
@@ -328,7 +329,7 @@ class UpsertNotificationApi(Resource):
|
||||
def post(self):
|
||||
payload = UpsertNotificationPayload.model_validate(console_ns.payload)
|
||||
result = BillingService.upsert_notification(
|
||||
contents=[c.model_dump() for c in payload.contents],
|
||||
contents=[cast(LangContentDict, c.model_dump()) for c in payload.contents],
|
||||
frequency=payload.frequency,
|
||||
status=payload.status,
|
||||
notification_id=payload.notification_id,
|
||||
|
||||
@@ -5,6 +5,10 @@ from configs import dify_config
|
||||
from constants import DEFAULT_FILE_NUMBER_LIMITS
|
||||
|
||||
|
||||
class FeatureToggleDict(TypedDict):
|
||||
enabled: bool
|
||||
|
||||
|
||||
class SystemParametersDict(TypedDict):
|
||||
image_file_size_limit: int
|
||||
video_file_size_limit: int
|
||||
@@ -16,12 +20,12 @@ class SystemParametersDict(TypedDict):
|
||||
class AppParametersDict(TypedDict):
|
||||
opening_statement: str | None
|
||||
suggested_questions: list[str]
|
||||
suggested_questions_after_answer: dict[str, Any]
|
||||
speech_to_text: dict[str, Any]
|
||||
text_to_speech: dict[str, Any]
|
||||
retriever_resource: dict[str, Any]
|
||||
annotation_reply: dict[str, Any]
|
||||
more_like_this: dict[str, Any]
|
||||
suggested_questions_after_answer: FeatureToggleDict
|
||||
speech_to_text: FeatureToggleDict
|
||||
text_to_speech: FeatureToggleDict
|
||||
retriever_resource: FeatureToggleDict
|
||||
annotation_reply: FeatureToggleDict
|
||||
more_like_this: FeatureToggleDict
|
||||
user_input_form: list[dict[str, Any]]
|
||||
sensitive_word_avoidance: dict[str, Any]
|
||||
file_upload: dict[str, Any]
|
||||
|
||||
@@ -107,13 +107,13 @@ class AppGenerateResponseConverter(ABC):
|
||||
return metadata
|
||||
|
||||
@classmethod
|
||||
def _error_to_stream_response(cls, e: Exception):
|
||||
def _error_to_stream_response(cls, e: Exception) -> dict[str, Any]:
|
||||
"""
|
||||
Error to stream response.
|
||||
:param e: exception
|
||||
:return:
|
||||
"""
|
||||
error_responses = {
|
||||
error_responses: dict[type[Exception], dict[str, Any]] = {
|
||||
ValueError: {"code": "invalid_param", "status": 400},
|
||||
ProviderTokenNotInitError: {"code": "provider_not_initialize", "status": 400},
|
||||
QuotaExceededError: {
|
||||
@@ -127,7 +127,7 @@ class AppGenerateResponseConverter(ABC):
|
||||
}
|
||||
|
||||
# Determine the response based on the type of exception
|
||||
data = None
|
||||
data: dict[str, Any] | None = None
|
||||
for k, v in error_responses.items():
|
||||
if isinstance(e, k):
|
||||
data = v
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from graphon.entities import WorkflowNodeExecution
|
||||
from graphon.enums import WorkflowNodeExecutionStatus
|
||||
@@ -56,10 +56,22 @@ def create_links_from_trace_id(trace_id: str | None) -> list[Link]:
|
||||
return links
|
||||
|
||||
|
||||
def extract_retrieval_documents(documents: list[Document]) -> list[dict[str, Any]]:
|
||||
documents_data = []
|
||||
class RetrievalDocumentMetadataDict(TypedDict):
|
||||
dataset_id: Any
|
||||
doc_id: Any
|
||||
document_id: Any
|
||||
|
||||
|
||||
class RetrievalDocumentDict(TypedDict):
|
||||
content: str
|
||||
metadata: RetrievalDocumentMetadataDict
|
||||
score: Any
|
||||
|
||||
|
||||
def extract_retrieval_documents(documents: list[Document]) -> list[RetrievalDocumentDict]:
|
||||
documents_data: list[RetrievalDocumentDict] = []
|
||||
for document in documents:
|
||||
document_data = {
|
||||
document_data: RetrievalDocumentDict = {
|
||||
"content": document.page_content,
|
||||
"metadata": {
|
||||
"dataset_id": document.metadata.get("dataset_id"),
|
||||
@@ -83,7 +95,7 @@ def create_common_span_attributes(
|
||||
framework: str = DEFAULT_FRAMEWORK_NAME,
|
||||
inputs: str = "",
|
||||
outputs: str = "",
|
||||
) -> dict[str, Any]:
|
||||
) -> dict[str, str]:
|
||||
return {
|
||||
GEN_AI_SESSION_ID: session_id,
|
||||
GEN_AI_USER_ID: user_id,
|
||||
|
||||
@@ -240,7 +240,7 @@ class RetrievalService:
|
||||
@classmethod
|
||||
def _get_dataset(cls, dataset_id: str) -> Dataset | None:
|
||||
with Session(db.engine) as session:
|
||||
return session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
return session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
|
||||
@classmethod
|
||||
def keyword_search(
|
||||
@@ -573,15 +573,13 @@ class RetrievalService:
|
||||
|
||||
# Batch query summaries for segments retrieved via summary (only enabled summaries)
|
||||
if summary_segment_ids:
|
||||
summaries = (
|
||||
session.query(DocumentSegmentSummary)
|
||||
.filter(
|
||||
summaries = session.scalars(
|
||||
select(DocumentSegmentSummary).where(
|
||||
DocumentSegmentSummary.chunk_id.in_(list(summary_segment_ids)),
|
||||
DocumentSegmentSummary.status == "completed",
|
||||
DocumentSegmentSummary.enabled == True, # Only retrieve enabled summaries
|
||||
DocumentSegmentSummary.enabled.is_(True), # Only retrieve enabled summaries
|
||||
)
|
||||
.all()
|
||||
)
|
||||
).all()
|
||||
for summary in summaries:
|
||||
if summary.summary_content:
|
||||
segment_summary_map[summary.chunk_id] = summary.summary_content
|
||||
@@ -851,12 +849,12 @@ class RetrievalService:
|
||||
def get_segment_attachment_info(
|
||||
cls, dataset_id: str, tenant_id: str, attachment_id: str, session: Session
|
||||
) -> SegmentAttachmentResult | None:
|
||||
upload_file = session.query(UploadFile).where(UploadFile.id == attachment_id).first()
|
||||
upload_file = session.scalar(select(UploadFile).where(UploadFile.id == attachment_id).limit(1))
|
||||
if upload_file:
|
||||
attachment_binding = (
|
||||
session.query(SegmentAttachmentBinding)
|
||||
attachment_binding = session.scalar(
|
||||
select(SegmentAttachmentBinding)
|
||||
.where(SegmentAttachmentBinding.attachment_id == upload_file.id)
|
||||
.first()
|
||||
.limit(1)
|
||||
)
|
||||
if attachment_binding:
|
||||
attachment_info: AttachmentInfoDict = {
|
||||
@@ -875,14 +873,12 @@ class RetrievalService:
|
||||
cls, attachment_ids: list[str], session: Session
|
||||
) -> list[SegmentAttachmentInfoResult]:
|
||||
attachment_infos: list[SegmentAttachmentInfoResult] = []
|
||||
upload_files = session.query(UploadFile).where(UploadFile.id.in_(attachment_ids)).all()
|
||||
upload_files = session.scalars(select(UploadFile).where(UploadFile.id.in_(attachment_ids))).all()
|
||||
if upload_files:
|
||||
upload_file_ids = [upload_file.id for upload_file in upload_files]
|
||||
attachment_bindings = (
|
||||
session.query(SegmentAttachmentBinding)
|
||||
.where(SegmentAttachmentBinding.attachment_id.in_(upload_file_ids))
|
||||
.all()
|
||||
)
|
||||
attachment_bindings = session.scalars(
|
||||
select(SegmentAttachmentBinding).where(SegmentAttachmentBinding.attachment_id.in_(upload_file_ids))
|
||||
).all()
|
||||
attachment_binding_map = {binding.attachment_id: binding for binding in attachment_bindings}
|
||||
|
||||
if attachment_bindings:
|
||||
|
||||
@@ -37,6 +37,44 @@ class KnowledgeRateLimitDict(TypedDict):
|
||||
subscription_plan: str
|
||||
|
||||
|
||||
class TenantFeaturePlanUsageDict(TypedDict):
|
||||
result: str
|
||||
history_id: str
|
||||
|
||||
|
||||
class LangContentDict(TypedDict):
|
||||
lang: str
|
||||
title: str
|
||||
subtitle: str
|
||||
body: str
|
||||
title_pic_url: str
|
||||
|
||||
|
||||
class NotificationDict(TypedDict):
|
||||
notification_id: str
|
||||
contents: dict[str, LangContentDict]
|
||||
frequency: Literal["once", "every_page_load"]
|
||||
|
||||
|
||||
class AccountNotificationDict(TypedDict, total=False):
|
||||
should_show: bool
|
||||
notification: NotificationDict
|
||||
shouldShow: bool
|
||||
notifications: list[dict]
|
||||
|
||||
|
||||
class UpsertNotificationDict(TypedDict):
|
||||
notification_id: str
|
||||
|
||||
|
||||
class BatchAddNotificationAccountsDict(TypedDict):
|
||||
count: int
|
||||
|
||||
|
||||
class DismissNotificationDict(TypedDict):
|
||||
success: bool
|
||||
|
||||
|
||||
class BillingService:
|
||||
base_url = os.environ.get("BILLING_API_URL", "BILLING_API_URL")
|
||||
secret_key = os.environ.get("BILLING_API_SECRET_KEY", "BILLING_API_SECRET_KEY")
|
||||
@@ -94,7 +132,9 @@ class BillingService:
|
||||
return cls._send_request("GET", "/invoices", params=params)
|
||||
|
||||
@classmethod
|
||||
def update_tenant_feature_plan_usage(cls, tenant_id: str, feature_key: str, delta: int) -> dict:
|
||||
def update_tenant_feature_plan_usage(
|
||||
cls, tenant_id: str, feature_key: str, delta: int
|
||||
) -> TenantFeaturePlanUsageDict:
|
||||
"""
|
||||
Update tenant feature plan usage.
|
||||
|
||||
@@ -114,7 +154,7 @@ class BillingService:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def refund_tenant_feature_plan_usage(cls, history_id: str) -> dict:
|
||||
def refund_tenant_feature_plan_usage(cls, history_id: str) -> TenantFeaturePlanUsageDict:
|
||||
"""
|
||||
Refund a previous usage charge.
|
||||
|
||||
@@ -410,7 +450,7 @@ class BillingService:
|
||||
return tenant_whitelist
|
||||
|
||||
@classmethod
|
||||
def get_account_notification(cls, account_id: str) -> dict:
|
||||
def get_account_notification(cls, account_id: str) -> AccountNotificationDict:
|
||||
"""Return the active in-product notification for account_id, if any.
|
||||
|
||||
Calling this endpoint also marks the notification as seen; subsequent
|
||||
@@ -434,13 +474,13 @@ class BillingService:
|
||||
@classmethod
|
||||
def upsert_notification(
|
||||
cls,
|
||||
contents: list[dict],
|
||||
contents: list[LangContentDict],
|
||||
frequency: str = "once",
|
||||
status: str = "active",
|
||||
notification_id: str | None = None,
|
||||
start_time: str | None = None,
|
||||
end_time: str | None = None,
|
||||
) -> dict:
|
||||
) -> UpsertNotificationDict:
|
||||
"""Create or update a notification.
|
||||
|
||||
contents: list of {"lang": str, "title": str, "subtitle": str, "body": str, "title_pic_url": str}
|
||||
@@ -461,7 +501,9 @@ class BillingService:
|
||||
return cls._send_request("POST", "/notifications", json=payload)
|
||||
|
||||
@classmethod
|
||||
def batch_add_notification_accounts(cls, notification_id: str, account_ids: list[str]) -> dict:
|
||||
def batch_add_notification_accounts(
|
||||
cls, notification_id: str, account_ids: list[str]
|
||||
) -> BatchAddNotificationAccountsDict:
|
||||
"""Register target account IDs for a notification (max 1000 per call).
|
||||
|
||||
Returns {"count": int}.
|
||||
@@ -473,7 +515,7 @@ class BillingService:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dismiss_notification(cls, notification_id: str, account_id: str) -> dict:
|
||||
def dismiss_notification(cls, notification_id: str, account_id: str) -> DismissNotificationDict:
|
||||
"""Mark a notification as dismissed for an account.
|
||||
|
||||
Returns {"success": bool}.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Union
|
||||
from typing import Any, TypedDict, Union
|
||||
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
from graphon.model_runtime.entities.provider_entities import (
|
||||
@@ -25,6 +25,23 @@ from models.provider import LoadBalancingModelConfig, ProviderCredential, Provid
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadBalancingConfigDetailDict(TypedDict):
|
||||
id: str
|
||||
name: str
|
||||
credentials: dict[str, Any]
|
||||
enabled: bool
|
||||
|
||||
|
||||
class LoadBalancingConfigSummaryDict(TypedDict):
|
||||
id: str
|
||||
name: str
|
||||
credentials: dict[str, Any]
|
||||
credential_id: str | None
|
||||
enabled: bool
|
||||
in_cooldown: bool
|
||||
ttl: int
|
||||
|
||||
|
||||
class ModelLoadBalancingService:
|
||||
@staticmethod
|
||||
def _get_provider_manager(tenant_id: str) -> ProviderManager:
|
||||
@@ -74,7 +91,7 @@ class ModelLoadBalancingService:
|
||||
|
||||
def get_load_balancing_configs(
|
||||
self, tenant_id: str, provider: str, model: str, model_type: str, config_from: str = ""
|
||||
) -> tuple[bool, list[dict]]:
|
||||
) -> tuple[bool, list[LoadBalancingConfigSummaryDict]]:
|
||||
"""
|
||||
Get load balancing configurations.
|
||||
:param tenant_id: workspace id
|
||||
@@ -156,7 +173,7 @@ class ModelLoadBalancingService:
|
||||
decoding_rsa_key, decoding_cipher_rsa = encrypter.get_decrypt_decoding(tenant_id)
|
||||
|
||||
# fetch status and ttl for each config
|
||||
datas = []
|
||||
datas: list[LoadBalancingConfigSummaryDict] = []
|
||||
for load_balancing_config in load_balancing_configs:
|
||||
in_cooldown, ttl = LBModelManager.get_config_in_cooldown_and_ttl(
|
||||
tenant_id=tenant_id,
|
||||
@@ -214,7 +231,7 @@ class ModelLoadBalancingService:
|
||||
|
||||
def get_load_balancing_config(
|
||||
self, tenant_id: str, provider: str, model: str, model_type: str, config_id: str
|
||||
) -> dict | None:
|
||||
) -> LoadBalancingConfigDetailDict | None:
|
||||
"""
|
||||
Get load balancing configuration.
|
||||
:param tenant_id: workspace id
|
||||
@@ -267,12 +284,13 @@ class ModelLoadBalancingService:
|
||||
credentials=credentials, credential_form_schemas=credential_schemas.credential_form_schemas
|
||||
)
|
||||
|
||||
return {
|
||||
result: LoadBalancingConfigDetailDict = {
|
||||
"id": load_balancing_model_config.id,
|
||||
"name": load_balancing_model_config.name,
|
||||
"credentials": credentials,
|
||||
"enabled": load_balancing_model_config.enabled,
|
||||
}
|
||||
return result
|
||||
|
||||
def _init_inherit_config(
|
||||
self, tenant_id: str, provider: str, model: str, model_type: ModelType
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
import mimetypes
|
||||
import secrets
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from typing import Any, TypedDict
|
||||
from typing import Any, NotRequired, TypedDict
|
||||
|
||||
import orjson
|
||||
from flask import request
|
||||
@@ -58,6 +58,18 @@ class RawWebhookDataDict(TypedDict):
|
||||
files: dict[str, Any]
|
||||
|
||||
|
||||
class ValidationResultDict(TypedDict):
|
||||
valid: bool
|
||||
error: NotRequired[str]
|
||||
|
||||
|
||||
class WorkflowInputsDict(TypedDict):
|
||||
webhook_data: RawWebhookDataDict
|
||||
webhook_headers: dict[str, str]
|
||||
webhook_query_params: dict[str, str]
|
||||
webhook_body: dict[str, Any]
|
||||
|
||||
|
||||
class WebhookService:
|
||||
"""Service for handling webhook operations."""
|
||||
|
||||
@@ -173,7 +185,7 @@ class WebhookService:
|
||||
node_data = WebhookData.model_validate(node_config["data"], from_attributes=True)
|
||||
validation_result = cls._validate_http_metadata(raw_data, node_data)
|
||||
if not validation_result["valid"]:
|
||||
raise ValueError(validation_result["error"])
|
||||
raise ValueError(validation_result.get("error", "Validation failed"))
|
||||
|
||||
# Process and validate data according to configuration
|
||||
processed_data = cls._process_and_validate_data(raw_data, node_data)
|
||||
@@ -672,7 +684,7 @@ class WebhookService:
|
||||
raise ValueError(f"Required header missing: {header_name}")
|
||||
|
||||
@classmethod
|
||||
def _validate_http_metadata(cls, webhook_data: RawWebhookDataDict, node_data: WebhookData) -> dict[str, Any]:
|
||||
def _validate_http_metadata(cls, webhook_data: RawWebhookDataDict, node_data: WebhookData) -> ValidationResultDict:
|
||||
"""Validate HTTP method and content-type.
|
||||
|
||||
Args:
|
||||
@@ -716,7 +728,7 @@ class WebhookService:
|
||||
return content_type.split(";")[0].strip()
|
||||
|
||||
@classmethod
|
||||
def _validation_error(cls, error_message: str) -> dict[str, Any]:
|
||||
def _validation_error(cls, error_message: str) -> ValidationResultDict:
|
||||
"""Create a standard validation error response.
|
||||
|
||||
Args:
|
||||
@@ -737,7 +749,7 @@ class WebhookService:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def build_workflow_inputs(cls, webhook_data: RawWebhookDataDict) -> dict[str, Any]:
|
||||
def build_workflow_inputs(cls, webhook_data: RawWebhookDataDict) -> WorkflowInputsDict:
|
||||
"""Construct workflow inputs payload from webhook data.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -635,7 +635,7 @@ class WorkflowService:
|
||||
# If we can't determine the status, assume load balancing is not enabled
|
||||
return False
|
||||
|
||||
def _get_load_balancing_configs(self, tenant_id: str, provider: str, model_name: str) -> list[dict]:
|
||||
def _get_load_balancing_configs(self, tenant_id: str, provider: str, model_name: str) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all load balancing configurations for a model.
|
||||
|
||||
@@ -659,7 +659,7 @@ class WorkflowService:
|
||||
_, custom_configs = model_load_balancing_service.get_load_balancing_configs(
|
||||
tenant_id=tenant_id, provider=provider, model=model_name, model_type="llm", config_from="custom-model"
|
||||
)
|
||||
all_configs = configs + custom_configs
|
||||
all_configs = cast(list[dict[str, Any]], configs) + cast(list[dict[str, Any]], custom_configs)
|
||||
|
||||
return [config for config in all_configs if config.get("credential_id")]
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ def clean_notion_document_task(document_ids: list[str], dataset_id: str):
|
||||
total_index_node_ids = []
|
||||
|
||||
with session_factory.create_session() as session:
|
||||
dataset = session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
|
||||
if not dataset:
|
||||
raise Exception("Document has no dataset")
|
||||
@@ -41,7 +41,7 @@ def clean_notion_document_task(document_ids: list[str], dataset_id: str):
|
||||
total_index_node_ids.extend([segment.index_node_id for segment in segments])
|
||||
|
||||
with session_factory.create_session() as session:
|
||||
dataset = session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
if dataset:
|
||||
index_processor.clean(
|
||||
dataset, total_index_node_ids, with_keywords=True, delete_child_chunks=True, delete_summaries=True
|
||||
|
||||
@@ -28,7 +28,9 @@ def document_indexing_update_task(dataset_id: str, document_id: str):
|
||||
start_at = time.perf_counter()
|
||||
|
||||
with session_factory.create_session() as session, session.begin():
|
||||
document = session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first()
|
||||
document = session.scalar(
|
||||
select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1)
|
||||
)
|
||||
|
||||
if not document:
|
||||
logger.info(click.style(f"Document not found: {document_id}", fg="red"))
|
||||
@@ -37,7 +39,7 @@ def document_indexing_update_task(dataset_id: str, document_id: str):
|
||||
document.indexing_status = IndexingStatus.PARSING
|
||||
document.processing_started_at = naive_utc_now()
|
||||
|
||||
dataset = session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
if not dataset:
|
||||
return
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import time
|
||||
|
||||
import click
|
||||
from celery import shared_task
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from core.db.session_factory import session_factory
|
||||
from core.rag.index_processor.constant.index_type import IndexTechniqueType
|
||||
@@ -39,12 +40,12 @@ def generate_summary_index_task(dataset_id: str, document_id: str, segment_ids:
|
||||
|
||||
try:
|
||||
with session_factory.create_session() as session:
|
||||
dataset = session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
if not dataset:
|
||||
logger.error(click.style(f"Dataset not found: {dataset_id}", fg="red"))
|
||||
return
|
||||
|
||||
document = session.query(DatasetDocument).where(DatasetDocument.id == document_id).first()
|
||||
document = session.scalar(select(DatasetDocument).where(DatasetDocument.id == document_id).limit(1))
|
||||
if not document:
|
||||
logger.error(click.style(f"Document not found: {document_id}", fg="red"))
|
||||
return
|
||||
@@ -108,13 +109,12 @@ def generate_summary_index_task(dataset_id: str, document_id: str, segment_ids:
|
||||
if segment_ids:
|
||||
error_message = f"Summary generation failed: {str(e)}"
|
||||
with session_factory.create_session() as session:
|
||||
session.query(DocumentSegment).filter(
|
||||
DocumentSegment.id.in_(segment_ids),
|
||||
DocumentSegment.dataset_id == dataset_id,
|
||||
).update(
|
||||
{
|
||||
DocumentSegment.error: error_message,
|
||||
},
|
||||
synchronize_session=False,
|
||||
session.execute(
|
||||
update(DocumentSegment)
|
||||
.where(
|
||||
DocumentSegment.id.in_(segment_ids),
|
||||
DocumentSegment.dataset_id == dataset_id,
|
||||
)
|
||||
.values(error=error_message)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
import click
|
||||
from celery import shared_task # type: ignore
|
||||
from flask import current_app, g
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
@@ -118,20 +119,20 @@ def run_single_rag_pipeline_task(rag_pipeline_invoke_entity: Mapping[str, Any],
|
||||
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
# Load required entities
|
||||
account = session.query(Account).where(Account.id == user_id).first()
|
||||
account = session.scalar(select(Account).where(Account.id == user_id).limit(1))
|
||||
if not account:
|
||||
raise ValueError(f"Account {user_id} not found")
|
||||
|
||||
tenant = session.query(Tenant).where(Tenant.id == tenant_id).first()
|
||||
tenant = session.scalar(select(Tenant).where(Tenant.id == tenant_id).limit(1))
|
||||
if not tenant:
|
||||
raise ValueError(f"Tenant {tenant_id} not found")
|
||||
account.current_tenant = tenant
|
||||
|
||||
pipeline = session.query(Pipeline).where(Pipeline.id == pipeline_id).first()
|
||||
pipeline = session.scalar(select(Pipeline).where(Pipeline.id == pipeline_id).limit(1))
|
||||
if not pipeline:
|
||||
raise ValueError(f"Pipeline {pipeline_id} not found")
|
||||
|
||||
workflow = session.query(Workflow).where(Workflow.id == pipeline.workflow_id).first()
|
||||
workflow = session.scalar(select(Workflow).where(Workflow.id == pipeline.workflow_id).limit(1))
|
||||
if not workflow:
|
||||
raise ValueError(f"Workflow {pipeline.workflow_id} not found")
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import Any
|
||||
import click
|
||||
from celery import group, shared_task
|
||||
from flask import current_app, g
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
@@ -132,20 +133,20 @@ def run_single_rag_pipeline_task(rag_pipeline_invoke_entity: Mapping[str, Any],
|
||||
|
||||
with Session(db.engine) as session:
|
||||
# Load required entities
|
||||
account = session.query(Account).where(Account.id == user_id).first()
|
||||
account = session.scalar(select(Account).where(Account.id == user_id).limit(1))
|
||||
if not account:
|
||||
raise ValueError(f"Account {user_id} not found")
|
||||
|
||||
tenant = session.query(Tenant).where(Tenant.id == tenant_id).first()
|
||||
tenant = session.scalar(select(Tenant).where(Tenant.id == tenant_id).limit(1))
|
||||
if not tenant:
|
||||
raise ValueError(f"Tenant {tenant_id} not found")
|
||||
account.current_tenant = tenant
|
||||
|
||||
pipeline = session.query(Pipeline).where(Pipeline.id == pipeline_id).first()
|
||||
pipeline = session.scalar(select(Pipeline).where(Pipeline.id == pipeline_id).limit(1))
|
||||
if not pipeline:
|
||||
raise ValueError(f"Pipeline {pipeline_id} not found")
|
||||
|
||||
workflow = session.query(Workflow).where(Workflow.id == pipeline.workflow_id).first()
|
||||
workflow = session.scalar(select(Workflow).where(Workflow.id == pipeline.workflow_id).limit(1))
|
||||
if not workflow:
|
||||
raise ValueError(f"Workflow {pipeline.workflow_id} not found")
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import time
|
||||
|
||||
import click
|
||||
from celery import shared_task
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from core.db.session_factory import session_factory
|
||||
from core.rag.index_processor.index_processor_factory import IndexProcessorFactory
|
||||
@@ -26,7 +26,7 @@ def remove_document_from_index_task(document_id: str):
|
||||
start_at = time.perf_counter()
|
||||
|
||||
with session_factory.create_session() as session:
|
||||
document = session.query(Document).where(Document.id == document_id).first()
|
||||
document = session.scalar(select(Document).where(Document.id == document_id).limit(1))
|
||||
if not document:
|
||||
logger.info(click.style(f"Document not found: {document_id}", fg="red"))
|
||||
return
|
||||
@@ -68,13 +68,15 @@ def remove_document_from_index_task(document_id: str):
|
||||
except Exception:
|
||||
logger.exception("clean dataset %s from index failed", dataset.id)
|
||||
# update segment to disable
|
||||
session.query(DocumentSegment).where(DocumentSegment.document_id == document.id).update(
|
||||
{
|
||||
DocumentSegment.enabled: False,
|
||||
DocumentSegment.disabled_at: naive_utc_now(),
|
||||
DocumentSegment.disabled_by: document.disabled_by,
|
||||
DocumentSegment.updated_at: naive_utc_now(),
|
||||
}
|
||||
session.execute(
|
||||
update(DocumentSegment)
|
||||
.where(DocumentSegment.document_id == document.id)
|
||||
.values(
|
||||
enabled=False,
|
||||
disabled_at=naive_utc_now(),
|
||||
disabled_by=document.disabled_by,
|
||||
updated_at=naive_utc_now(),
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
@@ -32,15 +32,15 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_
|
||||
start_at = time.perf_counter()
|
||||
with session_factory.create_session() as session:
|
||||
try:
|
||||
dataset = session.query(Dataset).where(Dataset.id == dataset_id).first()
|
||||
dataset = session.scalar(select(Dataset).where(Dataset.id == dataset_id).limit(1))
|
||||
if not dataset:
|
||||
logger.info(click.style(f"Dataset not found: {dataset_id}", fg="red"))
|
||||
return
|
||||
user = session.query(Account).where(Account.id == user_id).first()
|
||||
user = session.scalar(select(Account).where(Account.id == user_id).limit(1))
|
||||
if not user:
|
||||
logger.info(click.style(f"User not found: {user_id}", fg="red"))
|
||||
return
|
||||
tenant = session.query(Tenant).where(Tenant.id == dataset.tenant_id).first()
|
||||
tenant = session.scalar(select(Tenant).where(Tenant.id == dataset.tenant_id).limit(1))
|
||||
if not tenant:
|
||||
raise ValueError("Tenant not found")
|
||||
user.current_tenant = tenant
|
||||
@@ -58,10 +58,8 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_
|
||||
"your subscription."
|
||||
)
|
||||
except Exception as e:
|
||||
document = (
|
||||
session.query(Document)
|
||||
.where(Document.id == document_id, Document.dataset_id == dataset_id)
|
||||
.first()
|
||||
document = session.scalar(
|
||||
select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1)
|
||||
)
|
||||
if document:
|
||||
document.indexing_status = IndexingStatus.ERROR
|
||||
@@ -73,8 +71,8 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str], user_
|
||||
return
|
||||
|
||||
logger.info(click.style(f"Start retry document: {document_id}", fg="green"))
|
||||
document = (
|
||||
session.query(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).first()
|
||||
document = session.scalar(
|
||||
select(Document).where(Document.id == document_id, Document.dataset_id == dataset_id).limit(1)
|
||||
)
|
||||
if not document:
|
||||
logger.info(click.style(f"Document not found: {document_id}", fg="yellow"))
|
||||
|
||||
@@ -119,6 +119,14 @@ class _FakeSummaryQuery:
|
||||
return self._summaries
|
||||
|
||||
|
||||
class _FakeScalarsResult:
|
||||
def __init__(self, data: list) -> None:
|
||||
self._data = data
|
||||
|
||||
def all(self) -> list:
|
||||
return self._data
|
||||
|
||||
|
||||
class _FakeSession:
|
||||
def __init__(self, execute_payloads: list[list], summaries: list) -> None:
|
||||
self._payloads = list(execute_payloads)
|
||||
@@ -128,8 +136,8 @@ class _FakeSession:
|
||||
data = self._payloads.pop(0) if self._payloads else []
|
||||
return _FakeExecuteResult(data)
|
||||
|
||||
def query(self, model):
|
||||
return _FakeSummaryQuery(self._summaries)
|
||||
def scalars(self, stmt):
|
||||
return _FakeScalarsResult(self._summaries)
|
||||
|
||||
|
||||
class _FakeSessionContext:
|
||||
@@ -265,14 +273,14 @@ class TestRetrievalServiceInternals:
|
||||
def test_get_dataset_queries_by_id(self, mock_session_class):
|
||||
expected_dataset = Mock(spec=Dataset)
|
||||
mock_session = Mock()
|
||||
mock_session.query.return_value.where.return_value.first.return_value = expected_dataset
|
||||
mock_session.scalar.return_value = expected_dataset
|
||||
mock_session_class.return_value.__enter__.return_value = mock_session
|
||||
|
||||
with patch.object(retrieval_service_module, "db", SimpleNamespace(engine=Mock())):
|
||||
result = RetrievalService._get_dataset("dataset-123")
|
||||
|
||||
assert result == expected_dataset
|
||||
mock_session.query.assert_called_once()
|
||||
mock_session.scalar.assert_called_once()
|
||||
|
||||
@patch("core.rag.datasource.retrieval_service.Keyword")
|
||||
@patch("core.rag.datasource.retrieval_service.RetrievalService._get_dataset")
|
||||
@@ -1046,12 +1054,8 @@ class TestRetrievalServiceInternals:
|
||||
size=42,
|
||||
)
|
||||
binding = SimpleNamespace(segment_id="segment-1", attachment_id="upload-1")
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.first.return_value = upload_file
|
||||
binding_query = Mock()
|
||||
binding_query.where.return_value.first.return_value = binding
|
||||
session = Mock()
|
||||
session.query.side_effect = [upload_query, binding_query]
|
||||
session.scalar.side_effect = [upload_file, binding]
|
||||
|
||||
result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session)
|
||||
|
||||
@@ -1076,32 +1080,26 @@ class TestRetrievalServiceInternals:
|
||||
mime_type="image/png",
|
||||
size=42,
|
||||
)
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.first.return_value = upload_file
|
||||
binding_query = Mock()
|
||||
binding_query.where.return_value.first.return_value = None
|
||||
session = Mock()
|
||||
session.query.side_effect = [upload_query, binding_query]
|
||||
session.scalar.side_effect = [upload_file, None]
|
||||
|
||||
result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_get_segment_attachment_info_returns_none_when_upload_file_missing(self):
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.first.return_value = None
|
||||
session = Mock()
|
||||
session.query.return_value = upload_query
|
||||
session.scalar.return_value = None
|
||||
|
||||
result = RetrievalService.get_segment_attachment_info("dataset-id", "tenant-id", "upload-1", session)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_get_segment_attachment_infos_returns_empty_when_upload_files_missing(self):
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.all.return_value = []
|
||||
scalars_result = Mock()
|
||||
scalars_result.all.return_value = []
|
||||
session = Mock()
|
||||
session.query.return_value = upload_query
|
||||
session.scalars.return_value = scalars_result
|
||||
|
||||
result = RetrievalService.get_segment_attachment_infos(["upload-1"], session)
|
||||
|
||||
@@ -1115,12 +1113,12 @@ class TestRetrievalServiceInternals:
|
||||
mime_type="image/png",
|
||||
size=42,
|
||||
)
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.all.return_value = [upload_file]
|
||||
binding_query = Mock()
|
||||
binding_query.where.return_value.all.return_value = []
|
||||
upload_scalars = Mock()
|
||||
upload_scalars.all.return_value = [upload_file]
|
||||
binding_scalars = Mock()
|
||||
binding_scalars.all.return_value = []
|
||||
session = Mock()
|
||||
session.query.side_effect = [upload_query, binding_query]
|
||||
session.scalars.side_effect = [upload_scalars, binding_scalars]
|
||||
|
||||
result = RetrievalService.get_segment_attachment_infos(["upload-1"], session)
|
||||
|
||||
@@ -1144,12 +1142,12 @@ class TestRetrievalServiceInternals:
|
||||
)
|
||||
binding = SimpleNamespace(attachment_id="upload-1", segment_id="segment-1")
|
||||
|
||||
upload_query = Mock()
|
||||
upload_query.where.return_value.all.return_value = [upload_file_1, upload_file_2]
|
||||
binding_query = Mock()
|
||||
binding_query.where.return_value.all.return_value = [binding]
|
||||
upload_scalars = Mock()
|
||||
upload_scalars.all.return_value = [upload_file_1, upload_file_2]
|
||||
binding_scalars = Mock()
|
||||
binding_scalars.all.return_value = [binding]
|
||||
session = Mock()
|
||||
session.query.side_effect = [upload_query, binding_query]
|
||||
session.scalars.side_effect = [upload_scalars, binding_scalars]
|
||||
|
||||
result = RetrievalService.get_segment_attachment_infos(["upload-1", "upload-2"], session)
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import type { App } from '@/types/app'
|
||||
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCallback, useEffect, useId, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import TagSelector from '@/app/components/base/tag-management/selector'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
@@ -69,6 +70,7 @@ type AppCardProps = {
|
||||
|
||||
const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const deleteAppNameInputId = useId()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const { onPlanInfoChanged } = useProviderContext()
|
||||
@@ -89,14 +91,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
await mutateDeleteApp(app.id)
|
||||
toast.success(t('appDeleted', { ns: 'app' }))
|
||||
onPlanInfoChanged()
|
||||
setShowConfirmDelete(false)
|
||||
setConfirmDeleteInput('')
|
||||
}
|
||||
catch (e: any) {
|
||||
toast.error(`${t('appDeleteFailed', { ns: 'app' })}${'message' in e ? `: ${e.message}` : ''}`)
|
||||
}
|
||||
finally {
|
||||
setShowConfirmDelete(false)
|
||||
setConfirmDeleteInput('')
|
||||
}
|
||||
}, [app.id, mutateDeleteApp, onPlanInfoChanged, t])
|
||||
|
||||
const onDeleteDialogOpenChange = useCallback((open: boolean) => {
|
||||
@@ -108,6 +108,16 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
setConfirmDeleteInput('')
|
||||
}, [isDeleting])
|
||||
|
||||
const isDeleteConfirmDisabled = isDeleting || confirmDeleteInput !== app.name
|
||||
|
||||
const onDeleteDialogSubmit: React.FormEventHandler<HTMLFormElement> = useCallback((e) => {
|
||||
e.preventDefault()
|
||||
if (isDeleteConfirmDisabled)
|
||||
return
|
||||
|
||||
void onConfirmDelete()
|
||||
}, [isDeleteConfirmDisabled, onConfirmDelete])
|
||||
|
||||
const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({
|
||||
name,
|
||||
icon_type,
|
||||
@@ -503,38 +513,51 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
)}
|
||||
<AlertDialog open={showConfirmDelete} onOpenChange={onDeleteDialogOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pb-4 pt-6">
|
||||
<AlertDialogTitle className="text-text-primary title-2xl-semi-bold">
|
||||
{t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full whitespace-pre-wrap wrap-break-word text-text-tertiary system-md-regular">
|
||||
{t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
<div className="mt-2">
|
||||
<label className="mb-1 block text-text-secondary system-sm-regular">
|
||||
{t('deleteAppConfirmInputLabel', { ns: 'app', appName: app.name })}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="border-components-input-border bg-components-input-bg focus:border-components-input-border-focus focus:ring-components-input-border-focus h-9 w-full rounded-lg border px-3 text-sm text-text-primary placeholder:text-text-quaternary focus:outline-hidden focus:ring-1"
|
||||
placeholder={t('deleteAppConfirmInputPlaceholder', { ns: 'app' })}
|
||||
value={confirmDeleteInput}
|
||||
onChange={e => setConfirmDeleteInput(e.target.value)}
|
||||
/>
|
||||
<form className="flex flex-col" onSubmit={onDeleteDialogSubmit}>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t('deleteAppConfirmTitle', { ns: 'app' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('deleteAppConfirmContent', { ns: 'app' })}
|
||||
</AlertDialogDescription>
|
||||
<div className="mt-2">
|
||||
<label htmlFor={deleteAppNameInputId} className="mb-1 block system-sm-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey="deleteAppConfirmInputLabel"
|
||||
ns="app"
|
||||
values={{ appName: app.name }}
|
||||
components={{
|
||||
appName: <span className="system-sm-semibold text-text-primary" translate="no" />,
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<Input
|
||||
id={deleteAppNameInputId}
|
||||
name="confirm-app-name"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
placeholder={t('deleteAppConfirmInputPlaceholder', { ns: 'app' })}
|
||||
value={confirmDeleteInput}
|
||||
onChange={e => setConfirmDeleteInput(e.target.value)}
|
||||
className="border-components-input-border-hover bg-components-input-bg-normal focus:border-components-input-border-active focus:bg-components-input-bg-active"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton disabled={isDeleting}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
loading={isDeleting}
|
||||
disabled={isDeleting || confirmDeleteInput !== app.name}
|
||||
onClick={onConfirmDelete}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton type="button" disabled={isDeleting}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
type="submit"
|
||||
loading={isDeleting}
|
||||
disabled={isDeleteConfirmDisabled}
|
||||
>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{secretEnvList.length > 0 && (
|
||||
|
||||
@@ -32,7 +32,7 @@ export function AlertDialogContent({
|
||||
<BaseAlertDialog.Backdrop
|
||||
{...backdropProps}
|
||||
className={cn(
|
||||
'fixed inset-0 z-1002 bg-background-overlay',
|
||||
'inset-0 fixed z-1002 bg-background-overlay',
|
||||
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
overlayClassName,
|
||||
)}
|
||||
@@ -40,8 +40,8 @@ export function AlertDialogContent({
|
||||
<BaseAlertDialog.Popup
|
||||
{...popupProps}
|
||||
className={cn(
|
||||
'fixed left-1/2 top-1/2 z-1002 max-h-[calc(100vh-2rem)] w-[480px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-starting-style:scale-95 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
'fixed top-1/2 left-1/2 z-1002 max-h-[calc(100vh-2rem)] w-[480px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => (
|
||||
<ContextMenuTrigger
|
||||
aria-label="context menu trigger area"
|
||||
render={<button type="button" className="flex h-44 w-80 select-none items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center text-sm text-text-tertiary" />}
|
||||
render={<button type="button" className="flex h-44 w-80 items-center justify-center rounded-xl border border-divider-subtle bg-background-default-subtle px-6 text-center text-sm text-text-tertiary select-none" />}
|
||||
>
|
||||
{label}
|
||||
</ContextMenuTrigger>
|
||||
|
||||
@@ -33,7 +33,7 @@ export function DialogCloseButton({
|
||||
aria-label={ariaLabel}
|
||||
{...props}
|
||||
className={cn(
|
||||
'absolute right-6 top-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-components-input-border-hover disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'absolute top-6 right-6 z-10 flex h-5 w-5 cursor-pointer items-center justify-center rounded-2xl hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@@ -60,7 +60,7 @@ export function DialogContent({
|
||||
<BaseDialog.Backdrop
|
||||
{...backdropProps}
|
||||
className={cn(
|
||||
'fixed inset-0 z-1002 bg-background-overlay',
|
||||
'inset-0 fixed z-1002 bg-background-overlay',
|
||||
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
overlayClassName,
|
||||
backdropProps?.className,
|
||||
@@ -68,8 +68,8 @@ export function DialogContent({
|
||||
/>
|
||||
<BaseDialog.Popup
|
||||
className={cn(
|
||||
'fixed left-1/2 top-1/2 z-1002 max-h-[80dvh] w-[480px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-starting-style:scale-95 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
'fixed top-1/2 left-1/2 z-1002 max-h-[80dvh] w-[480px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl',
|
||||
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -186,7 +186,7 @@ export function DropdownMenuSubTrigger({
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<span aria-hidden className="i-ri-arrow-right-s-line ml-auto size-4 shrink-0 text-text-tertiary" />
|
||||
<span aria-hidden className="ml-auto i-ri-arrow-right-s-line size-4 shrink-0 text-text-tertiary" />
|
||||
</Menu.SubmenuTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ const FieldLabel = ({
|
||||
helperText,
|
||||
}: Pick<DemoFieldProps, 'label' | 'helperText'> & { inputId: string }) => (
|
||||
<div className="space-y-1">
|
||||
<label htmlFor={inputId} className="text-text-secondary system-sm-medium">
|
||||
<label htmlFor={inputId} className="system-sm-medium text-text-secondary">
|
||||
{label}
|
||||
</label>
|
||||
<p className="text-text-tertiary system-xs-regular">{helperText}</p>
|
||||
<p className="system-xs-regular text-text-tertiary">{helperText}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -97,7 +97,7 @@ const DemoField = ({
|
||||
</NumberFieldGroup>
|
||||
</NumberField>
|
||||
{showCurrentValue && (
|
||||
<p className="text-text-quaternary system-xs-regular">
|
||||
<p className="system-xs-regular text-text-quaternary">
|
||||
Current value:
|
||||
{' '}
|
||||
{formatValue ? formatValue(value) : formatNumericValue(value, unit)}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function NumberFieldInput({
|
||||
}
|
||||
|
||||
export const numberFieldUnitVariants = cva(
|
||||
'flex shrink-0 items-center self-stretch text-text-tertiary system-sm-regular',
|
||||
'flex shrink-0 items-center self-stretch system-sm-regular text-text-tertiary',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
@@ -133,9 +133,9 @@ export function NumberFieldControls({
|
||||
|
||||
const numberFieldControlButtonVariants = cva(
|
||||
[
|
||||
'flex touch-manipulation select-none items-center justify-center px-1.5 text-text-tertiary outline-hidden transition-colors',
|
||||
'flex touch-manipulation items-center justify-center px-1.5 text-text-tertiary outline-hidden transition-colors select-none',
|
||||
'hover:bg-components-input-bg-hover focus-visible:bg-components-input-bg-hover',
|
||||
'focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-components-input-border-active',
|
||||
'focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset',
|
||||
'disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:focus-visible:bg-transparent disabled:focus-visible:ring-0',
|
||||
'group-data-disabled/number-field:cursor-not-allowed hover:group-data-disabled/number-field:bg-transparent focus-visible:group-data-disabled/number-field:bg-transparent focus-visible:group-data-disabled/number-field:ring-0',
|
||||
'group-data-readonly/number-field:cursor-default hover:group-data-readonly/number-field:bg-transparent focus-visible:group-data-readonly/number-field:bg-transparent focus-visible:group-data-readonly/number-field:ring-0',
|
||||
|
||||
@@ -54,7 +54,7 @@ export function PopoverContent({
|
||||
<BasePopover.Popup
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
|
||||
'origin-(--transform-origin) transition-[transform,scale,opacity] data-ending-style:scale-95 data-starting-style:scale-95 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
'origin-(--transform-origin) transition-[transform,scale,opacity] data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
popupClassName,
|
||||
)}
|
||||
{...popupProps}
|
||||
|
||||
@@ -32,8 +32,8 @@ type Story = StoryObj<typeof meta>
|
||||
const panelClassName = 'overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg shadow-shadow-shadow-5'
|
||||
const blurPanelClassName = 'overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl shadow-shadow-shadow-7 backdrop-blur-[6px]'
|
||||
const labelClassName = 'text-text-tertiary system-xs-medium-uppercase tracking-[0.14em]'
|
||||
const titleClassName = 'text-text-primary system-sm-semibold'
|
||||
const bodyClassName = 'text-text-secondary system-sm-regular'
|
||||
const titleClassName = 'system-sm-semibold text-text-primary'
|
||||
const bodyClassName = 'system-sm-regular text-text-secondary'
|
||||
const insetScrollAreaClassName = 'h-full p-1'
|
||||
const insetViewportClassName = 'radius-3xl bg-components-panel-bg'
|
||||
const insetScrollbarClassName = 'data-[orientation=vertical]:my-1 data-[orientation=vertical]:me-1 data-[orientation=horizontal]:mx-1 data-[orientation=horizontal]:mb-1'
|
||||
@@ -126,8 +126,8 @@ const StoryCard = ({
|
||||
<section className={cn('min-w-0 radius-6xl border border-divider-subtle bg-background-body p-5', className)}>
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>{eyebrow}</div>
|
||||
<h3 className="text-pretty text-text-primary system-md-semibold">{title}</h3>
|
||||
<p className="max-w-[72ch] text-pretty text-text-secondary system-sm-regular">{description}</p>
|
||||
<h3 className="system-md-semibold text-pretty text-text-primary">{title}</h3>
|
||||
<p className="max-w-[72ch] system-sm-regular text-pretty text-text-secondary">{description}</p>
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
@@ -140,7 +140,7 @@ const VerticalPanelPane = () => (
|
||||
<ScrollAreaContent className="space-y-3 p-4 pr-6">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>Release board</div>
|
||||
<div className="text-text-primary system-md-semibold">Weekly checkpoints</div>
|
||||
<div className="system-md-semibold text-text-primary">Weekly checkpoints</div>
|
||||
<p className={bodyClassName}>A simple vertical panel with the default scrollbar skin and no business-specific overrides.</p>
|
||||
</div>
|
||||
{releaseRows.map(item => (
|
||||
@@ -148,9 +148,9 @@ const VerticalPanelPane = () => (
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<h4 className={cn(titleClassName, 'truncate')}>{item.title}</h4>
|
||||
<p className="text-text-tertiary system-xs-regular">{item.meta}</p>
|
||||
<p className="system-xs-regular text-text-tertiary">{item.meta}</p>
|
||||
</div>
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 text-text-secondary system-xs-medium">
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 system-xs-medium text-text-secondary">
|
||||
{item.status}
|
||||
</span>
|
||||
</div>
|
||||
@@ -170,14 +170,14 @@ const StickyListPane = () => (
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={cn(insetViewportClassName, 'mask-[linear-gradient(to_bottom,transparent_0px,black_10px,black_calc(100%-14px),transparent_100%)]')}>
|
||||
<ScrollAreaContent className="min-h-full">
|
||||
<div className="sticky top-0 z-10 border-b border-divider-subtle bg-components-panel-bg px-4 pb-3 pt-4">
|
||||
<div className="sticky top-0 z-10 border-b border-divider-subtle bg-components-panel-bg px-4 pt-4 pb-3">
|
||||
<div className={labelClassName}>Sticky header</div>
|
||||
<div className="mt-1 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-text-primary system-md-semibold">Operational queue</div>
|
||||
<p className="mt-1 text-text-secondary system-xs-regular">The scrollbar is still the shared base/ui primitive, while the pane adds sticky structure and a viewport mask.</p>
|
||||
<div className="system-md-semibold text-text-primary">Operational queue</div>
|
||||
<p className="mt-1 system-xs-regular text-text-secondary">The scrollbar is still the shared base/ui primitive, while the pane adds sticky structure and a viewport mask.</p>
|
||||
</div>
|
||||
<span className="rounded-lg border border-divider-subtle bg-components-panel-bg-alt px-2.5 py-1 text-text-secondary system-xs-medium">
|
||||
<span className="rounded-lg border border-divider-subtle bg-components-panel-bg-alt px-2.5 py-1 system-xs-medium text-text-secondary">
|
||||
24 items
|
||||
</span>
|
||||
</div>
|
||||
@@ -187,10 +187,10 @@ const StickyListPane = () => (
|
||||
<article key={item.id} className="rounded-xl border border-divider-subtle bg-components-panel-bg-alt px-3 py-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="truncate text-text-primary system-sm-semibold">{item.title}</div>
|
||||
<div className="line-clamp-2 wrap-break-word text-text-tertiary system-xs-regular">{item.note}</div>
|
||||
<div className="truncate system-sm-semibold text-text-primary">{item.title}</div>
|
||||
<div className="line-clamp-2 system-xs-regular wrap-break-word text-text-tertiary">{item.note}</div>
|
||||
</div>
|
||||
<span className="text-text-quaternary system-xs-medium">{item.id}</span>
|
||||
<span className="system-xs-medium text-text-quaternary">{item.id}</span>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
@@ -221,7 +221,7 @@ const WorkbenchPane = ({
|
||||
<ScrollAreaContent className="space-y-3 p-4 pr-6">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>{eyebrow}</div>
|
||||
<div className="text-text-primary system-md-semibold">{title}</div>
|
||||
<div className="system-md-semibold text-text-primary">{title}</div>
|
||||
</div>
|
||||
{children}
|
||||
</ScrollAreaContent>
|
||||
@@ -234,13 +234,13 @@ const WorkbenchPane = ({
|
||||
)
|
||||
|
||||
const HorizontalRailPane = () => (
|
||||
<div className={cn(panelClassName, 'h-[272px] min-w-0 max-w-full')}>
|
||||
<div className={cn(panelClassName, 'h-[272px] max-w-full min-w-0')}>
|
||||
<ScrollAreaRoot className={insetScrollAreaClassName}>
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="min-h-full min-w-max space-y-4 p-4 pb-6">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>Horizontal rail</div>
|
||||
<div className="text-text-primary system-md-semibold">Model lanes</div>
|
||||
<div className="system-md-semibold text-text-primary">Model lanes</div>
|
||||
<p className={bodyClassName}>This pane keeps the default track behavior and only changes the surface layout around it.</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
@@ -250,10 +250,10 @@ const HorizontalRailPane = () => (
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-state-base-hover text-text-secondary">
|
||||
<span aria-hidden className="i-ri-stack-line size-5" />
|
||||
</span>
|
||||
<div className="text-text-primary system-sm-semibold">{card.title}</div>
|
||||
<div className="text-text-secondary system-sm-regular">{card.detail}</div>
|
||||
<div className="system-sm-semibold text-text-primary">{card.title}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{card.detail}</div>
|
||||
</div>
|
||||
<div className="text-text-tertiary system-xs-regular">Drag cards into orchestration groups.</div>
|
||||
<div className="system-xs-regular text-text-tertiary">Drag cards into orchestration groups.</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -315,8 +315,8 @@ const ScrollbarStatePane = ({
|
||||
<div className="min-w-0 radius-6xl border border-divider-subtle bg-background-body p-5">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>{eyebrow}</div>
|
||||
<div className="text-text-primary system-md-semibold">{title}</div>
|
||||
<p className="text-text-secondary system-sm-regular">{description}</p>
|
||||
<div className="system-md-semibold text-text-primary">{title}</div>
|
||||
<p className="system-sm-regular text-text-secondary">{description}</p>
|
||||
</div>
|
||||
<div className="mt-4 min-w-0 rounded-[24px] border border-divider-subtle bg-components-panel-bg p-3">
|
||||
<ScrollAreaRoot className="h-[320px] p-1">
|
||||
@@ -324,8 +324,8 @@ const ScrollbarStatePane = ({
|
||||
<ScrollAreaContent className="min-w-0 space-y-2 p-4 pr-6">
|
||||
{scrollbarShowcaseRows.map(item => (
|
||||
<article key={item.title} className="min-w-0 rounded-xl border border-divider-subtle bg-components-panel-bg-alt p-3">
|
||||
<div className="truncate text-text-primary system-sm-semibold">{item.title}</div>
|
||||
<div className="mt-1 wrap-break-word text-text-secondary system-sm-regular">{item.body}</div>
|
||||
<div className="truncate system-sm-semibold text-text-primary">{item.title}</div>
|
||||
<div className="mt-1 system-sm-regular wrap-break-word text-text-secondary">{item.body}</div>
|
||||
</article>
|
||||
))}
|
||||
</ScrollAreaContent>
|
||||
@@ -343,22 +343,22 @@ const HorizontalScrollbarShowcasePane = () => (
|
||||
<div className="min-w-0 radius-6xl border border-divider-subtle bg-background-body p-5">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>Horizontal</div>
|
||||
<div className="text-text-primary system-md-semibold">Horizontal track reference</div>
|
||||
<p className="text-text-secondary system-sm-regular">Current design delivery defines the horizontal scrollbar body, but not a horizontal edge hint.</p>
|
||||
<div className="system-md-semibold text-text-primary">Horizontal track reference</div>
|
||||
<p className="system-sm-regular text-text-secondary">Current design delivery defines the horizontal scrollbar body, but not a horizontal edge hint.</p>
|
||||
</div>
|
||||
<div className="mt-4 min-w-0 rounded-[24px] border border-divider-subtle bg-components-panel-bg p-3">
|
||||
<ScrollAreaRoot className="h-[240px] p-1">
|
||||
<ScrollAreaViewport className="radius-3xl bg-components-panel-bg">
|
||||
<ScrollAreaContent className="min-h-full min-w-max space-y-4 p-4 pb-6">
|
||||
<div className="space-y-1">
|
||||
<div className="text-text-primary system-sm-semibold">Horizontal scrollbar</div>
|
||||
<div className="text-text-secondary system-sm-regular">A clean horizontal pane to inspect thickness, padding, and thumb behavior without extra masks.</div>
|
||||
<div className="system-sm-semibold text-text-primary">Horizontal scrollbar</div>
|
||||
<div className="system-sm-regular text-text-secondary">A clean horizontal pane to inspect thickness, padding, and thumb behavior without extra masks.</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
{horizontalShowcaseCards.map(card => (
|
||||
<article key={card.title} className="flex h-[120px] w-[220px] shrink-0 flex-col justify-between rounded-2xl border border-divider-subtle bg-components-panel-bg-alt p-4">
|
||||
<div className="text-text-primary system-sm-semibold">{card.title}</div>
|
||||
<div className="text-text-secondary system-sm-regular">{card.body}</div>
|
||||
<div className="system-sm-semibold text-text-primary">{card.title}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{card.body}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -380,7 +380,7 @@ const OverlayPane = () => (
|
||||
<ScrollAreaContent className="space-y-2 p-3 pr-6">
|
||||
<div className="sticky top-0 z-10 rounded-xl border border-divider-subtle bg-components-panel-bg-blur px-3 py-3 backdrop-blur-[6px]">
|
||||
<div className={labelClassName}>Overlay palette</div>
|
||||
<div className="mt-1 text-text-primary system-md-semibold">Quick actions</div>
|
||||
<div className="mt-1 system-md-semibold text-text-primary">Quick actions</div>
|
||||
</div>
|
||||
{activityRows.map(item => (
|
||||
<article key={item.title} className="rounded-xl border border-divider-subtle bg-components-panel-bg px-3 py-3 shadow-sm shadow-shadow-shadow-2">
|
||||
@@ -389,8 +389,8 @@ const OverlayPane = () => (
|
||||
<span aria-hidden className="i-ri-flashlight-line size-4" />
|
||||
</span>
|
||||
<div className="space-y-1">
|
||||
<div className="text-text-primary system-sm-semibold">{item.title}</div>
|
||||
<div className="text-text-secondary system-xs-regular">{item.body}</div>
|
||||
<div className="system-sm-semibold text-text-primary">{item.title}</div>
|
||||
<div className="system-xs-regular text-text-secondary">{item.body}</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -413,22 +413,22 @@ const CornerPane = () => (
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div className="space-y-1">
|
||||
<div className={labelClassName}>Corner surface</div>
|
||||
<div className="text-text-primary system-md-semibold">Bi-directional inspector canvas</div>
|
||||
<div className="system-md-semibold text-text-primary">Bi-directional inspector canvas</div>
|
||||
<p className={bodyClassName}>Both axes overflow here so the corner becomes visible as a deliberate seam between the two tracks.</p>
|
||||
</div>
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 text-text-secondary system-xs-medium">
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 system-xs-medium text-text-secondary">
|
||||
Always visible
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid min-w-[560px] grid-cols-[220px_repeat(3,180px)] gap-3">
|
||||
{Array.from({ length: 12 }, (_, index) => (
|
||||
<article key={index} className="rounded-2xl border border-divider-subtle bg-components-panel-bg-alt p-4">
|
||||
<div className="text-text-primary system-sm-semibold">
|
||||
<div className="system-sm-semibold text-text-primary">
|
||||
Cell
|
||||
{' '}
|
||||
{index + 1}
|
||||
</div>
|
||||
<p className="mt-2 text-text-secondary system-sm-regular">
|
||||
<p className="mt-2 system-sm-regular text-text-secondary">
|
||||
Wide-and-tall content to force both scrollbars and show the corner treatment clearly.
|
||||
</p>
|
||||
</article>
|
||||
@@ -458,7 +458,7 @@ const ExploreSidebarWebAppsPane = () => {
|
||||
<div className="flex size-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid">
|
||||
<span className="i-ri-apps-fill size-3.5 text-components-avatar-shape-fill-stop-100" />
|
||||
</div>
|
||||
<div className="min-w-0 truncate text-components-menu-item-text-active system-sm-semibold">
|
||||
<div className="min-w-0 truncate system-sm-semibold text-components-menu-item-text-active">
|
||||
Explore
|
||||
</div>
|
||||
</div>
|
||||
@@ -466,10 +466,10 @@ const ExploreSidebarWebAppsPane = () => {
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between gap-3 px-2">
|
||||
<p className="min-w-0 uppercase text-text-tertiary system-xs-medium-uppercase">
|
||||
<p className="min-w-0 system-xs-medium-uppercase text-text-tertiary uppercase">
|
||||
Web Apps
|
||||
</p>
|
||||
<span className="shrink-0 text-text-quaternary system-xs-medium">
|
||||
<span className="shrink-0 system-xs-medium text-text-quaternary">
|
||||
{webAppsRows.length}
|
||||
</span>
|
||||
</div>
|
||||
@@ -555,7 +555,7 @@ export const ThreePaneWorkbench: Story = {
|
||||
{releaseRows.map(item => (
|
||||
<button key={item.title} type="button" className={storyButtonClassName}>
|
||||
<span className="min-w-0 truncate system-sm-medium">{item.title}</span>
|
||||
<span className="text-text-quaternary system-xs-medium">{item.status}</span>
|
||||
<span className="system-xs-medium text-text-quaternary">{item.status}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -565,16 +565,16 @@ export const ThreePaneWorkbench: Story = {
|
||||
{Array.from({ length: 7 }, (_, index) => (
|
||||
<section key={index} className="rounded-2xl border border-divider-subtle bg-components-panel-bg-alt p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-text-primary system-sm-semibold">
|
||||
<div className="system-sm-semibold text-text-primary">
|
||||
Section
|
||||
{' '}
|
||||
{index + 1}
|
||||
</div>
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 text-text-secondary system-xs-medium">
|
||||
<span className="rounded-full bg-state-base-hover px-2 py-1 system-xs-medium text-text-secondary">
|
||||
Active
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-text-secondary system-sm-regular">
|
||||
<p className="mt-2 system-sm-regular text-text-secondary">
|
||||
This pane is intentionally long so the default vertical scrollbar sits over a larger editorial surface.
|
||||
</p>
|
||||
</section>
|
||||
@@ -585,9 +585,9 @@ export const ThreePaneWorkbench: Story = {
|
||||
<div className="space-y-3">
|
||||
{queueRows.map(item => (
|
||||
<article key={item.id} className="rounded-xl border border-divider-subtle bg-components-panel-bg-alt p-3">
|
||||
<div className="text-text-primary system-sm-semibold">{item.id}</div>
|
||||
<div className="mt-1 text-text-secondary system-sm-regular">{item.title}</div>
|
||||
<div className="mt-2 text-text-tertiary system-xs-regular">{item.note}</div>
|
||||
<div className="system-sm-semibold text-text-primary">{item.id}</div>
|
||||
<div className="mt-1 system-sm-regular text-text-secondary">{item.title}</div>
|
||||
<div className="mt-2 system-xs-regular text-text-tertiary">{item.note}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
@@ -658,7 +658,7 @@ export const PrimitiveComposition: Story = {
|
||||
<ScrollAreaViewport className={insetViewportClassName}>
|
||||
<ScrollAreaContent className="min-w-[560px] space-y-3 p-4 pr-6">
|
||||
{Array.from({ length: 8 }, (_, index) => (
|
||||
<div key={index} className="rounded-xl border border-divider-subtle bg-components-panel-bg-alt px-3 py-3 text-text-secondary system-sm-regular">
|
||||
<div key={index} className="rounded-xl border border-divider-subtle bg-components-panel-bg-alt px-3 py-3 system-sm-regular text-text-secondary">
|
||||
Primitive row
|
||||
{' '}
|
||||
{index + 1}
|
||||
|
||||
@@ -26,7 +26,7 @@ type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
|
||||
|
||||
const scrollAreaScrollbarClassName = cn(
|
||||
styles.scrollbar,
|
||||
'flex touch-none select-none overflow-clip p-1 opacity-100 transition-opacity motion-reduce:transition-none',
|
||||
'flex touch-none overflow-clip p-1 opacity-100 transition-opacity select-none motion-reduce:transition-none',
|
||||
'pointer-events-none data-hovering:pointer-events-auto',
|
||||
'data-scrolling:pointer-events-auto',
|
||||
'data-[orientation=vertical]:absolute data-[orientation=vertical]:inset-y-0 data-[orientation=vertical]:w-3 data-[orientation=vertical]:justify-center',
|
||||
@@ -41,7 +41,7 @@ const scrollAreaThumbClassName = cn(
|
||||
|
||||
const scrollAreaViewportClassName = cn(
|
||||
'size-full min-h-0 min-w-0 outline-hidden',
|
||||
'focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-components-input-border-hover',
|
||||
'focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:ring-inset',
|
||||
)
|
||||
|
||||
const scrollAreaCornerClassName = 'bg-transparent'
|
||||
|
||||
@@ -53,7 +53,7 @@ function SliderDemo({
|
||||
onValueChange={setValue}
|
||||
aria-label="Demo slider"
|
||||
/>
|
||||
<div className="text-center text-text-secondary system-sm-medium">
|
||||
<div className="text-center system-sm-medium text-text-secondary">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ type SliderProps = ControlledSliderProps | UncontrolledSliderProps
|
||||
|
||||
const sliderRootClassName = 'group/slider relative inline-flex w-full data-disabled:opacity-30'
|
||||
const sliderControlClassName = cn(
|
||||
'relative flex h-5 w-full touch-none select-none items-center',
|
||||
'relative flex h-5 w-full touch-none items-center select-none',
|
||||
'data-disabled:cursor-not-allowed',
|
||||
)
|
||||
const sliderTrackClassName = cn(
|
||||
@@ -45,7 +45,7 @@ const sliderThumbClassName = cn(
|
||||
'bg-(--slider-knob,var(--color-components-slider-knob)) shadow-sm',
|
||||
'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none',
|
||||
'hover:bg-(--slider-knob-hover,var(--color-components-slider-knob-hover))',
|
||||
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0',
|
||||
'focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0 focus-visible:outline-hidden',
|
||||
'active:shadow-md',
|
||||
'group-data-disabled/slider:bg-(--slider-knob-disabled,var(--color-components-slider-knob-disabled))',
|
||||
'group-data-disabled/slider:border-(--slider-knob-border,var(--color-components-slider-knob-border))',
|
||||
|
||||
@@ -41,8 +41,8 @@ export function TooltipContent({
|
||||
>
|
||||
<BaseTooltip.Popup
|
||||
className={cn(
|
||||
variant === 'default' && 'max-w-[300px] wrap-break-word rounded-md bg-components-panel-bg px-3 py-2 text-left text-text-tertiary shadow-lg system-xs-regular',
|
||||
'origin-(--transform-origin) transition-opacity data-ending-style:opacity-0 data-starting-style:opacity-0 data-instant:transition-none motion-reduce:transition-none',
|
||||
variant === 'default' && 'max-w-[300px] rounded-md bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg',
|
||||
'origin-(--transform-origin) transition-opacity data-ending-style:opacity-0 data-instant:transition-none data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
popupClassName,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1388,7 +1388,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 15
|
||||
"count": 10
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@@ -3813,71 +3813,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/alert-dialog/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/context-menu/index.stories.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/dialog/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/dropdown-menu/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/number-field/index.stories.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/number-field/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/popover/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/scroll-area/index.stories.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 46
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/scroll-area/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/select/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/slider/index.stories.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/slider/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/tooltip/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "إنشاء تطبيق",
|
||||
"createFromConfigFile": "إنشاء من ملف DSL",
|
||||
"deleteAppConfirmContent": "حذف التطبيق لا رجعة فيه. لن يتمكن المستخدمون من الوصول إلى تطبيقك بعد الآن، وسيتم حذف جميع تكوينات المطالبة والسجلات بشكل دائم.",
|
||||
"deleteAppConfirmInputLabel": "للتأكيد، اكتب \"{{appName}}\" في المربع أدناه:",
|
||||
"deleteAppConfirmInputPlaceholder": "أدخل اسم التطبيق",
|
||||
"deleteAppConfirmInputLabel": "للتأكيد، اكتب <appName>{{appName}}</appName> في المربع أدناه:",
|
||||
"deleteAppConfirmInputPlaceholder": "أدخل اسم التطبيق…",
|
||||
"deleteAppConfirmTitle": "حذف هذا التطبيق؟",
|
||||
"dslUploader.browse": "تصفح",
|
||||
"dslUploader.button": "اسحب وأفلت الملف، أو",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "Neue App erstellen",
|
||||
"createFromConfigFile": "App aus Konfigurationsdatei erstellen",
|
||||
"deleteAppConfirmContent": "Das Löschen der App ist unwiderruflich. Nutzer werden keinen Zugang mehr zu Ihrer App haben, und alle Prompt-Konfigurationen und Logs werden dauerhaft gelöscht.",
|
||||
"deleteAppConfirmInputLabel": "Geben Sie zur Bestätigung \"{{appName}}\" in das Feld unten ein:",
|
||||
"deleteAppConfirmInputPlaceholder": "App-Namen eingeben",
|
||||
"deleteAppConfirmInputLabel": "Geben Sie zur Bestätigung <appName>{{appName}}</appName> in das Feld unten ein:",
|
||||
"deleteAppConfirmInputPlaceholder": "App-Namen eingeben…",
|
||||
"deleteAppConfirmTitle": "Diese App löschen?",
|
||||
"dslUploader.browse": "Durchsuchen",
|
||||
"dslUploader.button": "Datei per Drag & Drop ablegen oder",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CREATE APP",
|
||||
"createFromConfigFile": "Create from DSL file",
|
||||
"deleteAppConfirmContent": "Deleting the app is irreversible. Users will no longer be able to access your app, and all prompt configurations and logs will be permanently deleted.",
|
||||
"deleteAppConfirmInputLabel": "To confirm, type \"{{appName}}\" in the box below:",
|
||||
"deleteAppConfirmInputPlaceholder": "Enter app name",
|
||||
"deleteAppConfirmInputLabel": "To confirm, type <appName>{{appName}}</appName> in the box below:",
|
||||
"deleteAppConfirmInputPlaceholder": "Enter app name…",
|
||||
"deleteAppConfirmTitle": "Delete this app?",
|
||||
"dslUploader.browse": "Browse",
|
||||
"dslUploader.button": "Drag and drop file, or",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CREAR APP",
|
||||
"createFromConfigFile": "Crear desde archivo DSL",
|
||||
"deleteAppConfirmContent": "Eliminar la app es irreversible. Los usuarios ya no podrán acceder a tu app y todas las configuraciones y registros de prompts se eliminarán permanentemente.",
|
||||
"deleteAppConfirmInputLabel": "Para confirmar, escriba \"{{appName}}\" en el cuadro a continuación:",
|
||||
"deleteAppConfirmInputPlaceholder": "Ingrese el nombre de la app",
|
||||
"deleteAppConfirmInputLabel": "Para confirmar, escriba <appName>{{appName}}</appName> en el cuadro a continuación:",
|
||||
"deleteAppConfirmInputPlaceholder": "Ingrese el nombre de la app…",
|
||||
"deleteAppConfirmTitle": "¿Eliminar esta app?",
|
||||
"dslUploader.browse": "Examinar",
|
||||
"dslUploader.button": "Arrastrar y soltar archivo, o",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "ایجاد برنامه",
|
||||
"createFromConfigFile": "ایجاد از فایل DSL",
|
||||
"deleteAppConfirmContent": "حذف برنامه غیرقابل برگشت است. کاربران دیگر قادر به دسترسی به برنامه شما نخواهند بود و تمام تنظیمات و گزارشات درخواستها به صورت دائم حذف خواهند شد.",
|
||||
"deleteAppConfirmInputLabel": "برای تأیید، \"{{appName}}\" را در کادر زیر تایپ کنید:",
|
||||
"deleteAppConfirmInputPlaceholder": "نام برنامه را وارد کنید",
|
||||
"deleteAppConfirmInputLabel": "برای تأیید، <appName>{{appName}}</appName> را در کادر زیر تایپ کنید:",
|
||||
"deleteAppConfirmInputPlaceholder": "نام برنامه را وارد کنید…",
|
||||
"deleteAppConfirmTitle": "آیا این برنامه حذف شود؟",
|
||||
"dslUploader.browse": "مرور",
|
||||
"dslUploader.button": "فایل را بکشید و رها کنید، یا",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CRÉER UNE APPLICATION",
|
||||
"createFromConfigFile": "Créer à partir du fichier DSL",
|
||||
"deleteAppConfirmContent": "La suppression de l'application est irréversible. Les utilisateurs ne pourront plus accéder à votre application et toutes les configurations de prompt et les journaux seront définitivement supprimés.",
|
||||
"deleteAppConfirmInputLabel": "Pour confirmer, tapez \"{{appName}}\" dans la case ci-dessous :",
|
||||
"deleteAppConfirmInputPlaceholder": "Entrez le nom de l'application",
|
||||
"deleteAppConfirmInputLabel": "Pour confirmer, tapez <appName>{{appName}}</appName> dans la case ci-dessous :",
|
||||
"deleteAppConfirmInputPlaceholder": "Entrez le nom de l'application…",
|
||||
"deleteAppConfirmTitle": "Supprimer cette application ?",
|
||||
"dslUploader.browse": "Parcourir",
|
||||
"dslUploader.button": "Glisser-déposer un fichier, ou",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "ऐप बनाएँ",
|
||||
"createFromConfigFile": "डीएसएल फ़ाइल से बनाएँ",
|
||||
"deleteAppConfirmContent": "ऐप को हटाना अपरिवर्तनीय है। उपयोगकर्ता अब आपके ऐप तक पहुँचने में सक्षम नहीं होंगे, और सभी प्रॉम्प्ट कॉन्फ़िगरेशन और लॉग स्थायी रूप से हटा दिए जाएंगे।",
|
||||
"deleteAppConfirmInputLabel": "पुष्टि करने के लिए, नीचे दिए गए बॉक्स में \"{{appName}}\" टाइप करें:",
|
||||
"deleteAppConfirmInputPlaceholder": "ऐप का नाम दर्ज करें",
|
||||
"deleteAppConfirmInputLabel": "पुष्टि करने के लिए, नीचे दिए गए बॉक्स में <appName>{{appName}}</appName> टाइप करें:",
|
||||
"deleteAppConfirmInputPlaceholder": "ऐप का नाम दर्ज करें…",
|
||||
"deleteAppConfirmTitle": "इस ऐप को हटाएँ?",
|
||||
"dslUploader.browse": "ब्राउज़ करें",
|
||||
"dslUploader.button": "फ़ाइल खींचकर छोड़ें, या",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "BUAT APLIKASI",
|
||||
"createFromConfigFile": "Buat dari file DSL",
|
||||
"deleteAppConfirmContent": "Menghapus aplikasi tidak dapat diubah. Pengguna tidak akan dapat lagi mengakses aplikasi Anda, dan semua konfigurasi prompt serta log akan dihapus secara permanen.",
|
||||
"deleteAppConfirmInputLabel": "Untuk konfirmasi, ketik \"{{appName}}\" di kotak di bawah ini:",
|
||||
"deleteAppConfirmInputPlaceholder": "Masukkan nama aplikasi",
|
||||
"deleteAppConfirmInputLabel": "Untuk konfirmasi, ketik <appName>{{appName}}</appName> di kotak di bawah ini:",
|
||||
"deleteAppConfirmInputPlaceholder": "Masukkan nama aplikasi…",
|
||||
"deleteAppConfirmTitle": "Hapus aplikasi ini?",
|
||||
"dslUploader.browse": "Ramban",
|
||||
"dslUploader.button": "Seret dan lepas file, atau",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CREA APP",
|
||||
"createFromConfigFile": "Crea da file DSL",
|
||||
"deleteAppConfirmContent": "Eliminare l'app è irreversibile. Gli utenti non potranno più accedere alla tua app e tutte le configurazioni e i log dei prompt verranno eliminati permanentemente.",
|
||||
"deleteAppConfirmInputLabel": "Per confermare, digita \"{{appName}}\" nel campo sottostante:",
|
||||
"deleteAppConfirmInputPlaceholder": "Inserisci il nome dell'app",
|
||||
"deleteAppConfirmInputLabel": "Per confermare, digita <appName>{{appName}}</appName> nel campo sottostante:",
|
||||
"deleteAppConfirmInputPlaceholder": "Inserisci il nome dell'app…",
|
||||
"deleteAppConfirmTitle": "Eliminare questa app?",
|
||||
"dslUploader.browse": "Sfoglia",
|
||||
"dslUploader.button": "Trascina e rilascia il file, o",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "アプリを作成する",
|
||||
"createFromConfigFile": "DSL ファイルから作成する",
|
||||
"deleteAppConfirmContent": "アプリを削除すると、元に戻すことはできません。他のユーザーはもはやこのアプリにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。",
|
||||
"deleteAppConfirmInputLabel": "確認するには、下のボックスに「{{appName}}」と入力してください:",
|
||||
"deleteAppConfirmInputPlaceholder": "アプリ名を入力",
|
||||
"deleteAppConfirmInputLabel": "確認するには、下のボックスに<appName>{{appName}}</appName>と入力してください:",
|
||||
"deleteAppConfirmInputPlaceholder": "アプリ名を入力…",
|
||||
"deleteAppConfirmTitle": "このアプリを削除しますか?",
|
||||
"dslUploader.browse": "参照",
|
||||
"dslUploader.button": "ファイルをドラッグ&ドロップするか、",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "앱 만들기",
|
||||
"createFromConfigFile": "DSL 파일에서 생성하기",
|
||||
"deleteAppConfirmContent": "앱을 삭제하면 복구할 수 없습니다. 사용자는 더 이상 앱에 액세스할 수 없으며 모든 프롬프트 설정 및 로그가 영구적으로 삭제됩니다.",
|
||||
"deleteAppConfirmInputLabel": "확인하려면 아래 상자에 \"{{appName}}\"을 입력하세요:",
|
||||
"deleteAppConfirmInputPlaceholder": "앱 이름 입력",
|
||||
"deleteAppConfirmInputLabel": "확인하려면 아래 상자에 <appName>{{appName}}</appName>을 입력하세요:",
|
||||
"deleteAppConfirmInputPlaceholder": "앱 이름 입력…",
|
||||
"deleteAppConfirmTitle": "이 앱을 삭제하시겠습니까?",
|
||||
"dslUploader.browse": "찾아보기",
|
||||
"dslUploader.button": "파일을 드래그 앤 드롭하거나",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CREATE APP",
|
||||
"createFromConfigFile": "Create from DSL file",
|
||||
"deleteAppConfirmContent": "Deleting the app is irreversible. Users will no longer be able to access your app, and all prompt configurations and logs will be permanently deleted.",
|
||||
"deleteAppConfirmInputLabel": "To confirm, type \"{{appName}}\" in the box below:",
|
||||
"deleteAppConfirmInputPlaceholder": "Enter app name",
|
||||
"deleteAppConfirmInputLabel": "To confirm, type <appName>{{appName}}</appName> in the box below:",
|
||||
"deleteAppConfirmInputPlaceholder": "Enter app name…",
|
||||
"deleteAppConfirmTitle": "Delete this app?",
|
||||
"dslUploader.browse": "Browse",
|
||||
"dslUploader.button": "Drag and drop file, or",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "UTWÓRZ APLIKACJĘ",
|
||||
"createFromConfigFile": "Utwórz z pliku DSL",
|
||||
"deleteAppConfirmContent": "Usunięcie aplikacji jest nieodwracalne. Użytkownicy nie będą mieli już dostępu do twojej aplikacji, a wszystkie konfiguracje monitów i dzienniki zostaną trwale usunięte.",
|
||||
"deleteAppConfirmInputLabel": "Aby potwierdzić, wpisz \"{{appName}}\" w polu poniżej:",
|
||||
"deleteAppConfirmInputPlaceholder": "Wpisz nazwę aplikacji",
|
||||
"deleteAppConfirmInputLabel": "Aby potwierdzić, wpisz <appName>{{appName}}</appName> w polu poniżej:",
|
||||
"deleteAppConfirmInputPlaceholder": "Wpisz nazwę aplikacji…",
|
||||
"deleteAppConfirmTitle": "Usunąć tę aplikację?",
|
||||
"dslUploader.browse": "Przeglądaj",
|
||||
"dslUploader.button": "Przeciągnij i upuść plik, lub",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CRIAR APLICATIVO",
|
||||
"createFromConfigFile": "Criar a partir do arquivo DSL",
|
||||
"deleteAppConfirmContent": "A exclusão do aplicativo é irreversível. Os usuários não poderão mais acessar seu aplicativo e todas as configurações de prompt e logs serão permanentemente excluídas.",
|
||||
"deleteAppConfirmInputLabel": "Para confirmar, digite \"{{appName}}\" na caixa abaixo:",
|
||||
"deleteAppConfirmInputPlaceholder": "Digite o nome do aplicativo",
|
||||
"deleteAppConfirmInputLabel": "Para confirmar, digite <appName>{{appName}}</appName> na caixa abaixo:",
|
||||
"deleteAppConfirmInputPlaceholder": "Digite o nome do aplicativo…",
|
||||
"deleteAppConfirmTitle": "Excluir este aplicativo?",
|
||||
"dslUploader.browse": "Navegar",
|
||||
"dslUploader.button": "Arraste e solte o arquivo, ou",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "CREEAZĂ APLICAȚIE",
|
||||
"createFromConfigFile": "Creează din fișier DSL",
|
||||
"deleteAppConfirmContent": "Ștergerea aplicației este ireversibilă. Utilizatorii nu vor mai putea accesa aplicația ta, iar toate configurațiile promptului și jurnalele vor fi șterse permanent.",
|
||||
"deleteAppConfirmInputLabel": "Pentru confirmare, tastați \"{{appName}}\" în caseta de mai jos:",
|
||||
"deleteAppConfirmInputPlaceholder": "Introduceți numele aplicației",
|
||||
"deleteAppConfirmInputLabel": "Pentru confirmare, tastați <appName>{{appName}}</appName> în caseta de mai jos:",
|
||||
"deleteAppConfirmInputPlaceholder": "Introduceți numele aplicației…",
|
||||
"deleteAppConfirmTitle": "Ștergi această aplicație?",
|
||||
"dslUploader.browse": "Răsfoiți",
|
||||
"dslUploader.button": "Trageți și plasați fișierul, sau",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "СОЗДАТЬ ПРИЛОЖЕНИЕ",
|
||||
"createFromConfigFile": "Создать из файла DSL",
|
||||
"deleteAppConfirmContent": "Удаление приложения необратимо. Пользователи больше не смогут получить доступ к вашему приложению, и все настройки подсказок и журналы будут безвозвратно удалены.",
|
||||
"deleteAppConfirmInputLabel": "Для подтверждения введите \"{{appName}}\" в поле ниже:",
|
||||
"deleteAppConfirmInputPlaceholder": "Введите название приложения",
|
||||
"deleteAppConfirmInputLabel": "Для подтверждения введите <appName>{{appName}}</appName> в поле ниже:",
|
||||
"deleteAppConfirmInputPlaceholder": "Введите название приложения…",
|
||||
"deleteAppConfirmTitle": "Удалить это приложение?",
|
||||
"dslUploader.browse": "Обзор",
|
||||
"dslUploader.button": "Перетащите файл, или",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "USTVARI APLIKACIJO",
|
||||
"createFromConfigFile": "Ustvari iz datoteke DSL",
|
||||
"deleteAppConfirmContent": "Brisanje aplikacije je nepopravljivo. Uporabniki ne bodo več imeli dostopa do vaše aplikacije, vse konfiguracije in dnevniki pa bodo trajno izbrisani.",
|
||||
"deleteAppConfirmInputLabel": "Za potrditev vnesite \"{{appName}}\" v polje spodaj:",
|
||||
"deleteAppConfirmInputPlaceholder": "Vnesite ime aplikacije",
|
||||
"deleteAppConfirmInputLabel": "Za potrditev vnesite <appName>{{appName}}</appName> v polje spodaj:",
|
||||
"deleteAppConfirmInputPlaceholder": "Vnesite ime aplikacije…",
|
||||
"deleteAppConfirmTitle": "Izbrišem to aplikacijo?",
|
||||
"dslUploader.browse": "Prebrskaj",
|
||||
"dslUploader.button": "Povlecite in spustite datoteko, ali",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "สร้างโปรเจกต์ใหม่",
|
||||
"createFromConfigFile": "สร้างจากไฟล์ DSL",
|
||||
"deleteAppConfirmContent": "การลบโปรเจกนั้นไม่สามารถย้อนกลับได้ ผู้ใช้จะไม่สามารถเข้าถึงโปรเจกต์ของคุณอีกต่อไป และการกําหนดค่าต่างๆและบันทึกทั้งหมดจะถูกลบอย่างถาวร",
|
||||
"deleteAppConfirmInputLabel": "หากต้องการยืนยัน พิมพ์ \"{{appName}}\" ในช่องด้านล่าง:",
|
||||
"deleteAppConfirmInputPlaceholder": "ใส่ชื่อแอป",
|
||||
"deleteAppConfirmInputLabel": "หากต้องการยืนยัน พิมพ์ <appName>{{appName}}</appName> ในช่องด้านล่าง:",
|
||||
"deleteAppConfirmInputPlaceholder": "ใส่ชื่อแอป…",
|
||||
"deleteAppConfirmTitle": "ลบโปรเจกต์นี้?",
|
||||
"dslUploader.browse": "เรียกดู",
|
||||
"dslUploader.button": "ลากและวางไฟล์ หรือ",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "UYGULAMA OLUŞTUR",
|
||||
"createFromConfigFile": "DSL dosyasından oluştur",
|
||||
"deleteAppConfirmContent": "Uygulamanın silinmesi geri alınamaz. Kullanıcılar artık uygulamanıza erişemeyecek ve tüm prompt yapılandırmaları ile loglar kalıcı olarak silinecektir.",
|
||||
"deleteAppConfirmInputLabel": "Onaylamak için aşağıdaki kutuya \"{{appName}}\" yazın:",
|
||||
"deleteAppConfirmInputPlaceholder": "Uygulama adını girin",
|
||||
"deleteAppConfirmInputLabel": "Onaylamak için aşağıdaki kutuya <appName>{{appName}}</appName> yazın:",
|
||||
"deleteAppConfirmInputPlaceholder": "Uygulama adını girin…",
|
||||
"deleteAppConfirmTitle": "Bu uygulamayı silmek istiyor musunuz?",
|
||||
"dslUploader.browse": "Gözat",
|
||||
"dslUploader.button": "Dosyayı sürükleyip bırakın veya",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "Створити додаток",
|
||||
"createFromConfigFile": "Створити з файлу DSL",
|
||||
"deleteAppConfirmContent": "Видалення додатка незворотнє. Користувачі більше не зможуть отримати доступ до вашого додатка, і всі налаштування запитів та журнали будуть остаточно видалені.",
|
||||
"deleteAppConfirmInputLabel": "Для підтвердження введіть \"{{appName}}\" у поле нижче:",
|
||||
"deleteAppConfirmInputPlaceholder": "Введіть назву додатка",
|
||||
"deleteAppConfirmInputLabel": "Для підтвердження введіть <appName>{{appName}}</appName> у поле нижче:",
|
||||
"deleteAppConfirmInputPlaceholder": "Введіть назву додатка…",
|
||||
"deleteAppConfirmTitle": "Видалити цей додаток?",
|
||||
"dslUploader.browse": "Огляд",
|
||||
"dslUploader.button": "Перетягніть файл, або",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "TẠO ỨNG DỤNG",
|
||||
"createFromConfigFile": "Tạo từ tệp DSL",
|
||||
"deleteAppConfirmContent": "Việc xóa ứng dụng là không thể hoàn tác. Người dùng sẽ không thể truy cập vào ứng dụng của bạn nữa và tất cả cấu hình cũng như nhật ký nhắc sẽ bị xóa vĩnh viễn.",
|
||||
"deleteAppConfirmInputLabel": "Để xác nhận, hãy nhập \"{{appName}}\" vào ô bên dưới:",
|
||||
"deleteAppConfirmInputPlaceholder": "Nhập tên ứng dụng",
|
||||
"deleteAppConfirmInputLabel": "Để xác nhận, hãy nhập <appName>{{appName}}</appName> vào ô bên dưới:",
|
||||
"deleteAppConfirmInputPlaceholder": "Nhập tên ứng dụng…",
|
||||
"deleteAppConfirmTitle": "Xóa ứng dụng này?",
|
||||
"dslUploader.browse": "Duyệt",
|
||||
"dslUploader.button": "Kéo và thả tệp, hoặc",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "创建应用",
|
||||
"createFromConfigFile": "通过 DSL 文件创建",
|
||||
"deleteAppConfirmContent": "删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。",
|
||||
"deleteAppConfirmInputLabel": "请在下方输入框中输入\"{{appName}}\"以确认:",
|
||||
"deleteAppConfirmInputPlaceholder": "输入应用名称",
|
||||
"deleteAppConfirmInputLabel": "请在下方输入框中输入<appName>{{appName}}</appName>以确认:",
|
||||
"deleteAppConfirmInputPlaceholder": "输入应用名称…",
|
||||
"deleteAppConfirmTitle": "确认删除应用?",
|
||||
"dslUploader.browse": "选择文件",
|
||||
"dslUploader.button": "拖拽文件至此,或者",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"createApp": "建立應用",
|
||||
"createFromConfigFile": "透過 DSL 檔案建立",
|
||||
"deleteAppConfirmContent": "刪除應用將無法復原。使用者將無法存取你的應用,所有 Prompt 設定和日誌都將一併被刪除。",
|
||||
"deleteAppConfirmInputLabel": "請在下方輸入框中輸入「{{appName}}」以確認:",
|
||||
"deleteAppConfirmInputPlaceholder": "輸入應用程式名稱",
|
||||
"deleteAppConfirmInputLabel": "請在下方輸入框中輸入<appName>{{appName}}</appName>以確認:",
|
||||
"deleteAppConfirmInputPlaceholder": "輸入應用程式名稱…",
|
||||
"deleteAppConfirmTitle": "確認刪除應用?",
|
||||
"dslUploader.browse": "選擇檔案",
|
||||
"dslUploader.button": "拖拽檔案至此,或者",
|
||||
|
||||
Reference in New Issue
Block a user