Compare commits

...

8 Commits

Author SHA1 Message Date
autofix-ci[bot]
a98c467a33 [autofix.ci] apply automated fixes 2026-03-10 10:14:46 +00:00
Yanli 盐粒
cb094558e3 Merge remote-tracking branch 'origin/main' into yanli/docx-image-render
# Conflicts:
#	api/tests/unit_tests/core/rag/extractor/test_word_extractor.py
2026-03-10 18:10:16 +08:00
Yanli 盐粒
60c858aa48 style: normalize import ordering in dataset model 2026-03-01 17:40:28 +08:00
Yanli 盐粒
46a3a2ae09 fix: sort signed URL replacements by position 2026-03-01 17:38:45 +08:00
Yanli 盐粒
30af50cb47 test: avoid MagicMock dependency in segment signing tests 2026-03-01 17:23:18 +08:00
Yanli 盐粒
0d6a4bac0f fix: harden segment file URL signing 2026-03-01 17:12:07 +08:00
autofix-ci[bot]
c693cb9789 [autofix.ci] apply automated fixes 2026-02-20 18:41:55 +00:00
Yanli 盐粒
fc91a7a38b Fix docx segment image URLs 2026-02-09 20:14:52 +08:00
174 changed files with 761 additions and 1381 deletions

View File

@@ -114,7 +114,6 @@ class PdfExtractor(BaseExtractor):
"""
image_content = []
upload_files = []
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
try:
image_objects = page.get_objects(filter=(pdfium_c.FPDF_PAGEOBJ_IMAGE,))
@@ -164,7 +163,7 @@ class PdfExtractor(BaseExtractor):
used_at=naive_utc_now(),
)
upload_files.append(upload_file)
image_content.append(f"![image]({base_url}/files/{upload_file.id}/file-preview)")
image_content.append(f"![image](/files/{upload_file.id}/file-preview)")
except Exception as e:
logger.warning("Failed to extract image from PDF: %s", e)
continue

View File

@@ -87,7 +87,6 @@ class WordExtractor(BaseExtractor):
def _extract_images_from_docx(self, doc):
image_count = 0
image_map = {}
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
for r_id, rel in doc.part.rels.items():
if "image" in rel.target_ref:
@@ -126,7 +125,7 @@ class WordExtractor(BaseExtractor):
used_at=naive_utc_now(),
)
db.session.add(upload_file)
image_map[r_id] = f"![image]({base_url}/files/{upload_file.id}/file-preview)"
image_map[r_id] = f"![image](/files/{upload_file.id}/file-preview)"
else:
image_ext = rel.target_ref.split(".")[-1]
if image_ext is None:
@@ -154,7 +153,7 @@ class WordExtractor(BaseExtractor):
used_at=naive_utc_now(),
)
db.session.add(upload_file)
image_map[rel.target_part] = f"![image]({base_url}/files/{upload_file.id}/file-preview)"
image_map[rel.target_part] = f"![image](/files/{upload_file.id}/file-preview)"
db.session.commit()
return image_map

View File

@@ -10,6 +10,7 @@ import re
import time
from datetime import datetime
from json import JSONDecodeError
from operator import itemgetter
from typing import Any, cast
from uuid import uuid4
@@ -806,41 +807,53 @@ class DocumentSegment(Base):
def sign_content(self) -> str:
return self.get_sign_content()
@staticmethod
def _build_signed_query_params(*, sign_target: str, upload_file_id: str) -> str:
nonce = os.urandom(16).hex()
timestamp = str(int(time.time()))
data_to_sign = f"{sign_target}|{upload_file_id}|{timestamp}|{nonce}"
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
encoded_sign = base64.urlsafe_b64encode(sign).decode()
return f"timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
def _get_accessible_upload_file_ids(self, upload_file_ids: set[str]) -> set[str]:
if not upload_file_ids:
return set()
matched_upload_file_ids = db.session.scalars(
select(UploadFile.id).where(
UploadFile.tenant_id == self.tenant_id,
UploadFile.id.in_(list(upload_file_ids)),
)
).all()
return {str(upload_file_id) for upload_file_id in matched_upload_file_ids}
def get_sign_content(self) -> str:
signed_urls: list[tuple[int, int, str]] = []
text = self.content
# For data before v0.10.0
pattern = r"/files/([a-f0-9\-]+)/image-preview(?:\?.*?)?"
matches = re.finditer(pattern, text)
for match in matches:
upload_file_id = match.group(1)
nonce = os.urandom(16).hex()
timestamp = str(int(time.time()))
data_to_sign = f"image-preview|{upload_file_id}|{timestamp}|{nonce}"
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
encoded_sign = base64.urlsafe_b64encode(sign).decode()
upload_file_preview_patterns = {
"image-preview": r"(?:https?://[^\s\)\"\']+)?/files/([a-f0-9\-]+)/image-preview(?:\?[^\s\)\"\']*)?",
"file-preview": r"(?:https?://[^\s\)\"\']+)?/files/([a-f0-9\-]+)/file-preview(?:\?[^\s\)\"\']*)?",
}
upload_file_matches: list[tuple[re.Match[str], str, str]] = []
upload_file_ids: set[str] = set()
params = f"timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
base_url = f"/files/{upload_file_id}/image-preview"
signed_url = f"{base_url}?{params}"
signed_urls.append((match.start(), match.end(), signed_url))
for preview_type, pattern in upload_file_preview_patterns.items():
for match in re.finditer(pattern, text):
upload_file_id = match.group(1)
upload_file_matches.append((match, preview_type, upload_file_id))
upload_file_ids.add(upload_file_id)
# For data after v0.10.0
pattern = r"/files/([a-f0-9\-]+)/file-preview(?:\?.*?)?"
matches = re.finditer(pattern, text)
for match in matches:
upload_file_id = match.group(1)
nonce = os.urandom(16).hex()
timestamp = str(int(time.time()))
data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
encoded_sign = base64.urlsafe_b64encode(sign).decode()
accessible_upload_file_ids = self._get_accessible_upload_file_ids(upload_file_ids)
params = f"timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
base_url = f"/files/{upload_file_id}/file-preview"
for match, preview_type, upload_file_id in upload_file_matches:
if upload_file_id not in accessible_upload_file_ids:
continue
params = self._build_signed_query_params(sign_target=preview_type, upload_file_id=upload_file_id)
base_url = f"/files/{upload_file_id}/{preview_type}"
signed_url = f"{base_url}?{params}"
signed_urls.append((match.start(), match.end(), signed_url))
@@ -851,19 +864,13 @@ class DocumentSegment(Base):
for match in matches:
upload_file_id = match.group(1)
file_extension = match.group(2)
nonce = os.urandom(16).hex()
timestamp = str(int(time.time()))
data_to_sign = f"file-preview|{upload_file_id}|{timestamp}|{nonce}"
secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b""
sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest()
encoded_sign = base64.urlsafe_b64encode(sign).decode()
params = f"timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}"
params = self._build_signed_query_params(sign_target="file-preview", upload_file_id=upload_file_id)
base_url = f"/files/tools/{upload_file_id}.{file_extension}"
signed_url = f"{base_url}?{params}"
signed_urls.append((match.start(), match.end(), signed_url))
# Reconstruct the text with signed URLs
signed_urls.sort(key=itemgetter(0))
offset = 0
for start, end, signed_url in signed_urls:
text = text[: start + offset] + signed_url + text[end + offset :]

View File

@@ -87,7 +87,7 @@ def test_extract_images_formats(mock_dependencies, monkeypatch, image_bytes, exp
mock_raw.FPDF_PAGEOBJ_IMAGE = 1
result = extractor._extract_images(mock_page)
assert f"![image](http://files.local/files/{file_id}/file-preview)" in result
assert f"![image](/files/{file_id}/file-preview)" in result
assert len(saves) == 1
assert saves[0][1] == image_bytes
assert len(db_stub.session.added) == 1
@@ -180,7 +180,7 @@ def test_extract_images_failures(mock_dependencies):
result = extractor._extract_images(mock_page)
# Should have one success
assert "![image](http://files.local/files/test_file_id/file-preview)" in result
assert "![image](/files/test_file_id/file-preview)" in result
assert len(saves) == 1
assert saves[0][1] == jpeg_bytes
assert db_stub.session.committed is True

View File

@@ -124,8 +124,7 @@ def test_extract_images_from_docx(monkeypatch):
db_stub = SimpleNamespace(session=DummySession())
monkeypatch.setattr(we, "db", db_stub)
# Patch config values used for URL composition and storage type
monkeypatch.setattr(we.dify_config, "FILES_URL", "http://files.local", raising=False)
# Patch config value used in this code path
monkeypatch.setattr(we.dify_config, "STORAGE_TYPE", "local", raising=False)
# Patch UploadFile to avoid real DB models
@@ -167,7 +166,7 @@ def test_extract_images_from_docx(monkeypatch):
# Returned map should contain entries for external (keyed by rId) and internal (keyed by target_part)
assert set(image_map.keys()) == {"rId1", internal_part}
assert all(v.startswith("![image](") and v.endswith("/file-preview)") for v in image_map.values())
assert all(v.startswith("![image](/files/") and v.endswith("/file-preview)") for v in image_map.values())
# Storage should receive both payloads
payloads = {data for _, data in saves}
@@ -179,37 +178,6 @@ def test_extract_images_from_docx(monkeypatch):
assert db_stub.session.committed is True
def test_extract_images_from_docx_uses_internal_files_url():
"""Test that INTERNAL_FILES_URL takes precedence over FILES_URL for plugin access."""
# Test the URL generation logic directly
from configs import dify_config
# Mock the configuration values
original_files_url = getattr(dify_config, "FILES_URL", None)
original_internal_files_url = getattr(dify_config, "INTERNAL_FILES_URL", None)
try:
# Set both URLs - INTERNAL should take precedence
dify_config.FILES_URL = "http://external.example.com"
dify_config.INTERNAL_FILES_URL = "http://internal.docker:5001"
# Test the URL generation logic (same as in word_extractor.py)
upload_file_id = "test_file_id"
# This is the pattern we fixed in the word extractor
base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL
generated_url = f"{base_url}/files/{upload_file_id}/file-preview"
# Verify that INTERNAL_FILES_URL is used instead of FILES_URL
assert "http://internal.docker:5001" in generated_url, f"Expected internal URL, got: {generated_url}"
assert "http://external.example.com" not in generated_url, f"Should not use external URL, got: {generated_url}"
finally:
# Restore original values
dify_config.FILES_URL = original_files_url
dify_config.INTERNAL_FILES_URL = original_internal_files_url
def test_extract_hyperlinks(monkeypatch):
# Mock db and storage to avoid issues during image extraction (even if no images are present)
monkeypatch.setattr(we, "storage", SimpleNamespace(save=lambda k, d: None))

View File

@@ -15,6 +15,7 @@ from datetime import UTC, datetime
from unittest.mock import patch
from uuid import uuid4
import models.dataset as dataset_module
from models.dataset import (
AppDatasetJoin,
ChildChunk,
@@ -489,6 +490,15 @@ class TestDocumentModelRelationships:
class TestDocumentSegmentIndexing:
"""Test suite for DocumentSegment model indexing and operations."""
@staticmethod
def _mock_scalars_result(upload_file_ids: list[str]):
class _ScalarsResult:
@staticmethod
def all() -> list[str]:
return upload_file_ids
return _ScalarsResult()
def test_document_segment_creation_with_required_fields(self):
"""Test creating a document segment with all required fields."""
# Arrange
@@ -547,6 +557,139 @@ class TestDocumentSegmentIndexing:
assert segment.index_node_hash == index_node_hash
assert segment.keywords == keywords
def test_document_segment_sign_content_strips_absolute_files_host(self):
"""Test that sign_content strips scheme/host from absolute /files URLs and returns a signed relative URL."""
# Arrange
upload_file_id = "1602650a-4fe4-423c-85a2-af76c083e3c4"
segment = DocumentSegment(
tenant_id=str(uuid4()),
dataset_id=str(uuid4()),
document_id=str(uuid4()),
position=1,
content=f"![image](http://internal.docker:5001/files/{upload_file_id}/file-preview)",
word_count=1,
tokens=1,
created_by=str(uuid4()),
)
mock_scalars_result = self._mock_scalars_result([upload_file_id])
# Act
with (
patch.object(dataset_module.dify_config, "SECRET_KEY", "secret", create=True),
patch("models.dataset.db.session.scalars", return_value=mock_scalars_result),
patch("models.dataset.time.time", return_value=1700000000),
patch("models.dataset.os.urandom", return_value=b"\x00" * 16),
):
signed = segment.get_sign_content()
# Assert
assert "internal.docker:5001" not in signed
assert f"/files/{upload_file_id}/file-preview?timestamp=" in signed
assert "&nonce=" in signed
assert "&sign=" in signed
def test_document_segment_sign_content_strips_absolute_files_host_for_image_preview(self):
"""Test that sign_content strips scheme/host from absolute image-preview URLs."""
# Arrange
upload_file_id = "e2a4f7b1-1234-5678-9abc-def012345678"
segment = DocumentSegment(
tenant_id=str(uuid4()),
dataset_id=str(uuid4()),
document_id=str(uuid4()),
position=1,
content=f"![image](http://internal.docker:5001/files/{upload_file_id}/image-preview)",
word_count=1,
tokens=1,
created_by=str(uuid4()),
)
mock_scalars_result = self._mock_scalars_result([upload_file_id])
# Act
with (
patch.object(dataset_module.dify_config, "SECRET_KEY", "secret", create=True),
patch("models.dataset.db.session.scalars", return_value=mock_scalars_result),
patch("models.dataset.time.time", return_value=1700000000),
patch("models.dataset.os.urandom", return_value=b"\x00" * 16),
):
signed = segment.get_sign_content()
# Assert
assert "internal.docker:5001" not in signed
assert f"/files/{upload_file_id}/image-preview?timestamp=" in signed
assert "&nonce=" in signed
assert "&sign=" in signed
def test_document_segment_sign_content_skips_upload_files_outside_tenant(self):
"""Test that sign_content only signs upload files belonging to the segment tenant."""
# Arrange
allowed_upload_file_id = "1602650a-4fe4-423c-85a2-af76c083e3c4"
denied_upload_file_id = "f8f35fca-568f-4626-adf0-4f30de96aa32"
segment = DocumentSegment(
tenant_id=str(uuid4()),
dataset_id=str(uuid4()),
document_id=str(uuid4()),
position=1,
content=(
f"allowed: ![image](/files/{allowed_upload_file_id}/file-preview) "
f"denied: ![image](/files/{denied_upload_file_id}/file-preview)"
),
word_count=1,
tokens=1,
created_by=str(uuid4()),
)
mock_scalars_result = self._mock_scalars_result([allowed_upload_file_id])
# Act
with (
patch.object(dataset_module.dify_config, "SECRET_KEY", "secret", create=True),
patch("models.dataset.db.session.scalars", return_value=mock_scalars_result),
patch("models.dataset.time.time", return_value=1700000000),
patch("models.dataset.os.urandom", return_value=b"\x00" * 16),
):
signed = segment.get_sign_content()
# Assert
assert f"/files/{allowed_upload_file_id}/file-preview?timestamp=" in signed
assert f"/files/{denied_upload_file_id}/file-preview?timestamp=" not in signed
assert f"/files/{denied_upload_file_id}/file-preview)" in signed
def test_document_segment_sign_content_handles_mixed_preview_order(self):
"""Test that sign_content preserves content when file-preview appears before image-preview."""
# Arrange
file_preview_id = "1602650a-4fe4-423c-85a2-af76c083e3c4"
image_preview_id = "e2a4f7b1-1234-5678-9abc-def012345678"
segment = DocumentSegment(
tenant_id=str(uuid4()),
dataset_id=str(uuid4()),
document_id=str(uuid4()),
position=1,
content=(
f"file-first: ![file](/files/{file_preview_id}/file-preview) "
f"then-image: ![image](/files/{image_preview_id}/image-preview)"
),
word_count=1,
tokens=1,
created_by=str(uuid4()),
)
mock_scalars_result = self._mock_scalars_result([file_preview_id, image_preview_id])
# Act
with (
patch.object(dataset_module.dify_config, "SECRET_KEY", "secret", create=True),
patch("models.dataset.db.session.scalars", return_value=mock_scalars_result),
patch("models.dataset.time.time", return_value=1700000000),
patch("models.dataset.os.urandom", return_value=b"\x00" * 16),
):
signed = segment.get_sign_content()
# Assert
file_signed = f"/files/{file_preview_id}/file-preview?timestamp="
image_signed = f"/files/{image_preview_id}/image-preview?timestamp="
assert file_signed in signed
assert image_signed in signed
assert signed.index(file_signed) < signed.index(image_signed)
assert signed.count("&sign=") == 2
def test_document_segment_with_answer_field(self):
"""Test creating a document segment with answer field for QA model."""
# Arrange

View File

@@ -333,7 +333,7 @@ describe('App Card Operations Flow', () => {
// Find and click the more button (popover trigger)
const moreIcons = document.querySelectorAll('svg')
const moreFill = Array.from(moreIcons).find(svg => svg.closest('[class*="cursor-pointer"]'))
const moreFill = [...moreIcons].find(svg => svg.closest('[class*="cursor-pointer"]'))
if (moreFill) {
const btn = moreFill.closest('[class*="cursor-pointer"]')
@@ -364,7 +364,7 @@ describe('App Card Operations Flow', () => {
renderAppCard({ id: 'app-edit', name: 'Editable App' })
const moreIcons = document.querySelectorAll('svg')
const moreFill = Array.from(moreIcons).find(svg => svg.closest('[class*="cursor-pointer"]'))
const moreFill = [...moreIcons].find(svg => svg.closest('[class*="cursor-pointer"]'))
if (moreFill) {
const btn = moreFill.closest('[class*="cursor-pointer"]')
@@ -400,7 +400,7 @@ describe('App Card Operations Flow', () => {
renderAppCard({ id: 'app-export', mode: AppModeEnum.COMPLETION, name: 'Export App' })
const moreIcons = document.querySelectorAll('svg')
const moreFill = Array.from(moreIcons).find(svg => svg.closest('[class*="cursor-pointer"]'))
const moreFill = [...moreIcons].find(svg => svg.closest('[class*="cursor-pointer"]'))
if (moreFill) {
const btn = moreFill.closest('[class*="cursor-pointer"]')
@@ -439,7 +439,7 @@ describe('App Card Operations Flow', () => {
renderAppCard({ id: 'app-switch', mode: AppModeEnum.CHAT })
const moreIcons = document.querySelectorAll('svg')
const moreFill = Array.from(moreIcons).find(svg => svg.closest('[class*="cursor-pointer"]'))
const moreFill = [...moreIcons].find(svg => svg.closest('[class*="cursor-pointer"]'))
if (moreFill) {
const btn = moreFill.closest('[class*="cursor-pointer"]')
@@ -456,7 +456,7 @@ describe('App Card Operations Flow', () => {
renderAppCard({ id: 'app-wf', mode: AppModeEnum.WORKFLOW, name: 'WF App' })
const moreIcons = document.querySelectorAll('svg')
const moreFill = Array.from(moreIcons).find(svg => svg.closest('[class*="cursor-pointer"]'))
const moreFill = [...moreIcons].find(svg => svg.closest('[class*="cursor-pointer"]'))
if (moreFill) {
const btn = moreFill.closest('[class*="cursor-pointer"]')

View File

@@ -588,7 +588,7 @@ export default translation
const trimmedKeyLine = keyLine.trim()
// If key line ends with ":" (not complete value), it's likely multiline
if (trimmedKeyLine.endsWith(':') && !trimmedKeyLine.includes('{') && !/:\s*['"`]/.exec(trimmedKeyLine)) {
if (trimmedKeyLine.endsWith(':') && !trimmedKeyLine.includes('{') && !/:\s*['"`]/.test(trimmedKeyLine)) {
// Find the value lines that belong to this key
let currentLine = targetLineIndex + 1
let foundValue = false
@@ -604,7 +604,7 @@ export default translation
}
// Check if this line starts a new key (indicates end of current value)
if (/^\w+\s*:/.exec(trimmed))
if (/^\w+\s*:/.test(trimmed))
break
// Check if this line is part of the value
@@ -632,7 +632,7 @@ export default translation
}
// Remove duplicates and sort in reverse order
const uniqueLinesToRemove = [...new Set(linesToRemove)].sort((a, b) => b - a)
const uniqueLinesToRemove = new Set(linesToRemove).toSorted((a, b) => b - a)
for (const lineIndex of uniqueLinesToRemove)
lines.splice(lineIndex, 1)

View File

@@ -109,7 +109,7 @@ describe('Document Management Flow', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.options.history).toBe('replace')
expect(update.searchParams.get('keyword')).toBe('test')
expect(update.searchParams.get('page')).toBe('2')
@@ -123,7 +123,7 @@ describe('Document Management Flow', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.options.history).toBe('replace')
expect(update.searchParams.toString()).toBe('')
})

View File

@@ -325,12 +325,12 @@ describe('Metadata Management Flow - Cross-Module Validation Composition', () =>
// Validate the same name multiple times for consistency
const name = 'consistent_field'
const results = Array.from({ length: 5 }, () => result.current.checkName(name))
const results = Array.from({ length: 5 }).fill(result.current.checkName(name))
expect(results.every(r => r.errorMsg === '')).toBe(true)
// Validate an invalid name multiple times
const invalidResults = Array.from({ length: 5 }, () => result.current.checkName('Invalid'))
const invalidResults = Array.from({ length: 5 }).fill(result.current.checkName('Invalid'))
expect(invalidResults.every(r => r.errorMsg !== '')).toBe(true)
})
})

View File

@@ -327,11 +327,11 @@ const ConfigPopup: FC<PopupProps> = ({
<div className="flex items-center justify-between">
<div className="flex items-center">
<TracingIcon size="md" className="mr-2" />
<div className="title-2xl-semi-bold text-text-primary">{t(`${I18N_PREFIX}.tracing`, { ns: 'app' })}</div>
<div className="text-text-primary title-2xl-semi-bold">{t(`${I18N_PREFIX}.tracing`, { ns: 'app' })}</div>
</div>
<div className="flex items-center">
<Indicator color={enabled ? 'green' : 'gray'} />
<div className={cn('system-xs-semibold-uppercase ml-1 text-text-tertiary', enabled && 'text-util-colors-green-green-600')}>
<div className={cn('ml-1 text-text-tertiary system-xs-semibold-uppercase', enabled && 'text-util-colors-green-green-600')}>
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`, { ns: 'app' })}
</div>
{!readOnly && (
@@ -350,7 +350,7 @@ const ConfigPopup: FC<PopupProps> = ({
</div>
</div>
<div className="system-xs-regular mt-2 text-text-tertiary">
<div className="mt-2 text-text-tertiary system-xs-regular">
{t(`${I18N_PREFIX}.tracingDescription`, { ns: 'app' })}
</div>
<Divider className="my-3" />
@@ -358,7 +358,7 @@ const ConfigPopup: FC<PopupProps> = ({
{(providerAllConfigured || providerAllNotConfigured)
? (
<>
<div className="system-xs-medium-uppercase text-text-tertiary">{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`, { ns: 'app' })}</div>
<div className="text-text-tertiary system-xs-medium-uppercase">{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`, { ns: 'app' })}</div>
<div className="mt-2 max-h-96 space-y-2 overflow-y-auto">
{langfusePanel}
{langSmithPanel}
@@ -375,11 +375,11 @@ const ConfigPopup: FC<PopupProps> = ({
)
: (
<>
<div className="system-xs-medium-uppercase text-text-tertiary">{t(`${I18N_PREFIX}.configProviderTitle.configured`, { ns: 'app' })}</div>
<div className="text-text-tertiary system-xs-medium-uppercase">{t(`${I18N_PREFIX}.configProviderTitle.configured`, { ns: 'app' })}</div>
<div className="mt-2 max-h-40 space-y-2 overflow-y-auto">
{configuredProviderPanel()}
</div>
<div className="system-xs-medium-uppercase mt-3 text-text-tertiary">{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`, { ns: 'app' })}</div>
<div className="mt-3 text-text-tertiary system-xs-medium-uppercase">{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`, { ns: 'app' })}</div>
<div className="mt-2 max-h-40 space-y-2 overflow-y-auto">
{moreProviderPanel()}
</div>

View File

@@ -160,7 +160,7 @@ const AvatarWithEdit = ({ onSave, ...props }: AvatarWithEditProps) => {
isShow={isShowDeleteConfirm}
onClose={() => setIsShowDeleteConfirm(false)}
>
<div className="title-2xl-semi-bold mb-3 text-text-primary">{t('avatar.deleteTitle', { ns: 'common' })}</div>
<div className="mb-3 text-text-primary title-2xl-semi-bold">{t('avatar.deleteTitle', { ns: 'common' })}</div>
<p className="mb-8 text-text-secondary">{t('avatar.deleteDescription', { ns: 'common' })}</p>
<div className="flex w-full items-center justify-center gap-2">

View File

@@ -209,14 +209,14 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
</div>
{step === STEP.start && (
<>
<div className="title-2xl-semi-bold pb-3 text-text-primary">{t('account.changeEmail.title', { ns: 'common' })}</div>
<div className="pb-3 text-text-primary title-2xl-semi-bold">{t('account.changeEmail.title', { ns: 'common' })}</div>
<div className="space-y-0.5 pb-2 pt-1">
<div className="body-md-medium text-text-warning">{t('account.changeEmail.authTip', { ns: 'common' })}</div>
<div className="body-md-regular text-text-secondary">
<div className="text-text-warning body-md-medium">{t('account.changeEmail.authTip', { ns: 'common' })}</div>
<div className="text-text-secondary body-md-regular">
<Trans
i18nKey="account.changeEmail.content1"
ns="common"
components={{ email: <span className="body-md-medium text-text-primary"></span> }}
components={{ email: <span className="text-text-primary body-md-medium"></span> }}
values={{ email }}
/>
</div>
@@ -241,19 +241,19 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
)}
{step === STEP.verifyOrigin && (
<>
<div className="title-2xl-semi-bold pb-3 text-text-primary">{t('account.changeEmail.verifyEmail', { ns: 'common' })}</div>
<div className="pb-3 text-text-primary title-2xl-semi-bold">{t('account.changeEmail.verifyEmail', { ns: 'common' })}</div>
<div className="space-y-0.5 pb-2 pt-1">
<div className="body-md-regular text-text-secondary">
<div className="text-text-secondary body-md-regular">
<Trans
i18nKey="account.changeEmail.content2"
ns="common"
components={{ email: <span className="body-md-medium text-text-primary"></span> }}
components={{ email: <span className="text-text-primary body-md-medium"></span> }}
values={{ email }}
/>
</div>
</div>
<div className="pt-3">
<div className="system-sm-medium mb-1 flex h-6 items-center text-text-secondary">{t('account.changeEmail.codeLabel', { ns: 'common' })}</div>
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-medium">{t('account.changeEmail.codeLabel', { ns: 'common' })}</div>
<Input
className="!w-full"
placeholder={t('account.changeEmail.codePlaceholder', { ns: 'common' })}
@@ -278,25 +278,25 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
{t('operation.cancel', { ns: 'common' })}
</Button>
</div>
<div className="system-xs-regular mt-3 flex items-center gap-1 text-text-tertiary">
<div className="mt-3 flex items-center gap-1 text-text-tertiary system-xs-regular">
<span>{t('account.changeEmail.resendTip', { ns: 'common' })}</span>
{time > 0 && (
<span>{t('account.changeEmail.resendCount', { ns: 'common', count: time })}</span>
)}
{!time && (
<span onClick={sendCodeToOriginEmail} className="system-xs-medium cursor-pointer text-text-accent-secondary">{t('account.changeEmail.resend', { ns: 'common' })}</span>
<span onClick={sendCodeToOriginEmail} className="cursor-pointer text-text-accent-secondary system-xs-medium">{t('account.changeEmail.resend', { ns: 'common' })}</span>
)}
</div>
</>
)}
{step === STEP.newEmail && (
<>
<div className="title-2xl-semi-bold pb-3 text-text-primary">{t('account.changeEmail.newEmail', { ns: 'common' })}</div>
<div className="pb-3 text-text-primary title-2xl-semi-bold">{t('account.changeEmail.newEmail', { ns: 'common' })}</div>
<div className="space-y-0.5 pb-2 pt-1">
<div className="body-md-regular text-text-secondary">{t('account.changeEmail.content3', { ns: 'common' })}</div>
<div className="text-text-secondary body-md-regular">{t('account.changeEmail.content3', { ns: 'common' })}</div>
</div>
<div className="pt-3">
<div className="system-sm-medium mb-1 flex h-6 items-center text-text-secondary">{t('account.changeEmail.emailLabel', { ns: 'common' })}</div>
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-medium">{t('account.changeEmail.emailLabel', { ns: 'common' })}</div>
<Input
className="!w-full"
placeholder={t('account.changeEmail.emailPlaceholder', { ns: 'common' })}
@@ -305,10 +305,10 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
destructive={newEmailExited || unAvailableEmail}
/>
{newEmailExited && (
<div className="body-xs-regular mt-1 py-0.5 text-text-destructive">{t('account.changeEmail.existingEmail', { ns: 'common' })}</div>
<div className="mt-1 py-0.5 text-text-destructive body-xs-regular">{t('account.changeEmail.existingEmail', { ns: 'common' })}</div>
)}
{unAvailableEmail && (
<div className="body-xs-regular mt-1 py-0.5 text-text-destructive">{t('account.changeEmail.unAvailableEmail', { ns: 'common' })}</div>
<div className="mt-1 py-0.5 text-text-destructive body-xs-regular">{t('account.changeEmail.unAvailableEmail', { ns: 'common' })}</div>
)}
</div>
<div className="mt-3 space-y-2">
@@ -331,19 +331,19 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
)}
{step === STEP.verifyNew && (
<>
<div className="title-2xl-semi-bold pb-3 text-text-primary">{t('account.changeEmail.verifyNew', { ns: 'common' })}</div>
<div className="pb-3 text-text-primary title-2xl-semi-bold">{t('account.changeEmail.verifyNew', { ns: 'common' })}</div>
<div className="space-y-0.5 pb-2 pt-1">
<div className="body-md-regular text-text-secondary">
<div className="text-text-secondary body-md-regular">
<Trans
i18nKey="account.changeEmail.content4"
ns="common"
components={{ email: <span className="body-md-medium text-text-primary"></span> }}
components={{ email: <span className="text-text-primary body-md-medium"></span> }}
values={{ email: mail }}
/>
</div>
</div>
<div className="pt-3">
<div className="system-sm-medium mb-1 flex h-6 items-center text-text-secondary">{t('account.changeEmail.codeLabel', { ns: 'common' })}</div>
<div className="mb-1 flex h-6 items-center text-text-secondary system-sm-medium">{t('account.changeEmail.codeLabel', { ns: 'common' })}</div>
<Input
className="!w-full"
placeholder={t('account.changeEmail.codePlaceholder', { ns: 'common' })}
@@ -368,13 +368,13 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
{t('operation.cancel', { ns: 'common' })}
</Button>
</div>
<div className="system-xs-regular mt-3 flex items-center gap-1 text-text-tertiary">
<div className="mt-3 flex items-center gap-1 text-text-tertiary system-xs-regular">
<span>{t('account.changeEmail.resendTip', { ns: 'common' })}</span>
{time > 0 && (
<span>{t('account.changeEmail.resendCount', { ns: 'common', count: time })}</span>
)}
{!time && (
<span onClick={sendCodeToNewEmail} className="system-xs-medium cursor-pointer text-text-accent-secondary">{t('account.changeEmail.resend', { ns: 'common' })}</span>
<span onClick={sendCodeToNewEmail} className="cursor-pointer text-text-accent-secondary system-xs-medium">{t('account.changeEmail.resend', { ns: 'common' })}</span>
)}
</div>
</>

View File

@@ -138,7 +138,7 @@ export default function AccountPage() {
imageUrl={icon_url}
/>
</div>
<div className="system-sm-medium mt-[3px] text-text-secondary">{item.name}</div>
<div className="mt-[3px] text-text-secondary system-sm-medium">{item.name}</div>
</div>
)
}
@@ -146,12 +146,12 @@ export default function AccountPage() {
return (
<>
<div className="pb-3 pt-2">
<h4 className="title-2xl-semi-bold text-text-primary">{t('account.myAccount', { ns: 'common' })}</h4>
<h4 className="text-text-primary title-2xl-semi-bold">{t('account.myAccount', { ns: 'common' })}</h4>
</div>
<div className="mb-8 flex items-center rounded-xl bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-6">
<AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={mutateUserProfile} size={64} />
<div className="ml-4">
<p className="system-xl-semibold text-text-primary">
<p className="text-text-primary system-xl-semibold">
{userProfile.name}
{isEducationAccount && (
<PremiumBadge size="s" color="blue" className="ml-1 !px-2">
@@ -160,16 +160,16 @@ export default function AccountPage() {
</PremiumBadge>
)}
</p>
<p className="system-xs-regular text-text-tertiary">{userProfile.email}</p>
<p className="text-text-tertiary system-xs-regular">{userProfile.email}</p>
</div>
</div>
<div className="mb-8">
<div className={titleClassName}>{t('account.name', { ns: 'common' })}</div>
<div className="mt-2 flex w-full items-center justify-between gap-2">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled ">
<div className="flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled system-sm-regular">
<span className="pl-1">{userProfile.name}</span>
</div>
<div className="system-sm-medium cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text" onClick={handleEditName}>
<div className="cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text system-sm-medium" onClick={handleEditName}>
{t('operation.edit', { ns: 'common' })}
</div>
</div>
@@ -177,11 +177,11 @@ export default function AccountPage() {
<div className="mb-8">
<div className={titleClassName}>{t('account.email', { ns: 'common' })}</div>
<div className="mt-2 flex w-full items-center justify-between gap-2">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled ">
<div className="flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled system-sm-regular">
<span className="pl-1">{userProfile.email}</span>
</div>
{systemFeatures.enable_change_email && (
<div className="system-sm-medium cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text" onClick={() => setShowUpdateEmail(true)}>
<div className="cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text system-sm-medium" onClick={() => setShowUpdateEmail(true)}>
{t('operation.change', { ns: 'common' })}
</div>
)}
@@ -191,8 +191,8 @@ export default function AccountPage() {
systemFeatures.enable_email_password_login && (
<div className="mb-8 flex justify-between gap-2">
<div>
<div className="system-sm-semibold mb-1 text-text-secondary">{t('account.password', { ns: 'common' })}</div>
<div className="body-xs-regular mb-2 text-text-tertiary">{t('account.passwordTip', { ns: 'common' })}</div>
<div className="mb-1 text-text-secondary system-sm-semibold">{t('account.password', { ns: 'common' })}</div>
<div className="mb-2 text-text-tertiary body-xs-regular">{t('account.passwordTip', { ns: 'common' })}</div>
</div>
<Button onClick={() => setEditPasswordModalVisible(true)}>{userProfile.is_password_set ? t('account.resetPassword', { ns: 'common' }) : t('account.setPassword', { ns: 'common' })}</Button>
</div>
@@ -219,7 +219,7 @@ export default function AccountPage() {
onClose={() => setEditNameModalVisible(false)}
className="!w-[420px] !p-6"
>
<div className="title-2xl-semi-bold mb-6 text-text-primary">{t('account.editName', { ns: 'common' })}</div>
<div className="mb-6 text-text-primary title-2xl-semi-bold">{t('account.editName', { ns: 'common' })}</div>
<div className={titleClassName}>{t('account.name', { ns: 'common' })}</div>
<Input
className="mt-2"
@@ -249,7 +249,7 @@ export default function AccountPage() {
}}
className="!w-[420px] !p-6"
>
<div className="title-2xl-semi-bold mb-6 text-text-primary">{userProfile.is_password_set ? t('account.resetPassword', { ns: 'common' }) : t('account.setPassword', { ns: 'common' })}</div>
<div className="mb-6 text-text-primary title-2xl-semi-bold">{userProfile.is_password_set ? t('account.resetPassword', { ns: 'common' }) : t('account.setPassword', { ns: 'common' })}</div>
{userProfile.is_password_set && (
<>
<div className={titleClassName}>{t('account.currentPassword', { ns: 'common' })}</div>
@@ -272,7 +272,7 @@ export default function AccountPage() {
</div>
</>
)}
<div className="system-sm-semibold mt-8 text-text-secondary">
<div className="mt-8 text-text-secondary system-sm-semibold">
{userProfile.is_password_set ? t('account.newPassword', { ns: 'common' }) : t('account.password', { ns: 'common' })}
</div>
<div className="relative mt-2">
@@ -291,7 +291,7 @@ export default function AccountPage() {
</Button>
</div>
</div>
<div className="system-sm-semibold mt-8 text-text-secondary">{t('account.confirmPassword', { ns: 'common' })}</div>
<div className="mt-8 text-text-secondary system-sm-semibold">{t('account.confirmPassword', { ns: 'common' })}</div>
<div className="relative mt-2">
<Input
type={showConfirmPassword ? 'text' : 'password'}

View File

@@ -61,7 +61,7 @@ function setupDomMeasurements(navWidth: number, moreWidth: number, childWidths:
if (this.id === 'more-measure')
return moreWidth
if (this.dataset.targetid) {
const idx = Array.from(this.parentElement?.children ?? []).indexOf(this)
const idx = [...this.parentElement?.children ?? []].indexOf(this)
return childWidths[idx] ?? 50
}
return 0
@@ -220,7 +220,7 @@ describe('AppOperations', () => {
const ops = [createOperation('edit', 'Edit')]
const { container } = render(<AppOperations gap={4} operations={ops} />)
const containers = container.querySelectorAll('div[style]')
const visibleContainer = Array.from(containers).find(
const visibleContainer = [...containers].find(
el => el.getAttribute('aria-hidden') !== 'true',
)
if (visibleContainer)

View File

@@ -103,7 +103,7 @@ describe('AppInfo', () => {
const { unmount } = render(<AppInfo expand={false} />)
const triggers = screen.getAllByTestId('trigger')
expect(triggers[triggers.length - 1]).toHaveAttribute('data-expand', 'false')
expect(triggers.at(-1)).toHaveAttribute('data-expand', 'false')
unmount()
})

View File

@@ -87,7 +87,7 @@ const AppOperations = ({
pre[cur.id] = false
return pre
}, {} as Record<string, boolean>)
const childrens = Array.from(navElement.children).slice(0, -1)
const childrens = [...navElement.children].slice(0, -1)
for (let i = 0; i < childrens.length; i++) {
const child = childrens[i] as HTMLElement
const id = child.dataset.targetid

View File

@@ -48,7 +48,7 @@ const CSVUploader: FC<Props> = ({
setDragging(false)
if (!e.dataTransfer)
return
const files = Array.from(e.dataTransfer.files)
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('stepOne.uploader.validation.count', { ns: 'datasetCreation' }) })
return
@@ -94,7 +94,7 @@ const CSVUploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('system-sm-regular flex h-20 items-center rounded-xl border border-dashed border-components-dropzone-border bg-components-dropzone-bg', dragging && 'border border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}>
<div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-dropzone-border bg-components-dropzone-bg system-sm-regular', dragging && 'border border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}>
<div className="flex w-full items-center justify-center space-x-2">
<CSVIcon className="shrink-0" />
<div className="text-text-tertiary">

View File

@@ -122,7 +122,7 @@ const AdvancedPromptInput: FC<Props> = ({
}
const handleBlur = () => {
const keys = getVars(value)
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.some(item => item.variable === key)).map(key => getNewVar(key, ''))
if (newPromptVariables.length > 0) {
setNewPromptVariables(newPromptVariables)
showConfirmAddVar()

View File

@@ -110,7 +110,7 @@ const Prompt: FC<ISimplePromptInput> = ({
// Filter out keys that are not properly defined (either not exist or exist but without valid name)
const newPromptVariables = keys.filter((key) => {
// Check if key exists in external data tools
if (externalDataToolsConfig.find((item: ExternalDataTool) => item.variable === key))
if (externalDataToolsConfig.some((item: ExternalDataTool) => item.variable === key))
return false
// Check if key exists in prompt variables
@@ -178,7 +178,7 @@ const Prompt: FC<ISimplePromptInput> = ({
{!noTitle && (
<div className="flex h-11 items-center justify-between pl-3 pr-2.5">
<div className="flex items-center space-x-1">
<div className="h2 system-sm-semibold-uppercase text-text-secondary">{mode !== AppModeEnum.COMPLETION ? t('chatSubTitle', { ns: 'appDebug' }) : t('completionSubTitle', { ns: 'appDebug' })}</div>
<div className="h2 text-text-secondary system-sm-semibold-uppercase">{mode !== AppModeEnum.COMPLETION ? t('chatSubTitle', { ns: 'appDebug' }) : t('completionSubTitle', { ns: 'appDebug' })}</div>
{!readonly && (
<Tooltip
popupContent={(

View File

@@ -35,11 +35,11 @@ const ConfigVision: FC = () => {
const newFeatures = produce(features, (draft) => {
if (value) {
draft.file!.allowed_file_types = Array.from(new Set([
draft.file!.allowed_file_types = [...new Set([
...(draft.file?.allowed_file_types || []),
SupportUploadFileTypes.image,
...(isAllowVideoUpload ? [SupportUploadFileTypes.video] : []),
]))
])]
}
else {
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
@@ -69,7 +69,7 @@ const ConfigVision: FC = () => {
</div>
</div>
<div className="flex grow items-center">
<div className="system-sm-semibold mr-1 text-text-secondary">{t('vision.name', { ns: 'appDebug' })}</div>
<div className="mr-1 text-text-secondary system-sm-semibold">{t('vision.name', { ns: 'appDebug' })}</div>
<Tooltip
popupContent={(
<div className="w-[180px]">
@@ -83,7 +83,7 @@ const ConfigVision: FC = () => {
? (
<>
<div className="mr-2 flex items-center gap-0.5">
<div className="system-xs-medium-uppercase text-text-tertiary">{t('vision.visionSettings.resolution', { ns: 'appDebug' })}</div>
<div className="text-text-tertiary system-xs-medium-uppercase">{t('vision.visionSettings.resolution', { ns: 'appDebug' })}</div>
<Tooltip
popupContent={(
<div className="w-[180px]">
@@ -100,7 +100,7 @@ const ConfigVision: FC = () => {
selected={file?.image?.detail === Resolution.high}
onSelect={noop}
className={cn(
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
file?.image?.detail !== Resolution.high && 'hover:border-components-option-card-option-border',
)}
/>
@@ -109,7 +109,7 @@ const ConfigVision: FC = () => {
selected={file?.image?.detail === Resolution.low}
onSelect={noop}
className={cn(
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
file?.image?.detail !== Resolution.low && 'hover:border-components-option-card-option-border',
)}
/>

View File

@@ -209,11 +209,11 @@ const AgentTools: FC = () => {
)}
<div
className={cn(
'system-xs-regular ml-1.5 flex w-0 grow items-center truncate',
'ml-1.5 flex w-0 grow items-center truncate system-xs-regular',
(item.isDeleted || item.notAuthor || !item.enabled) ? 'opacity-50' : '',
)}
>
<span className="system-xs-medium pr-1.5 text-text-secondary">{getProviderShowName(item)}</span>
<span className="pr-1.5 text-text-secondary system-xs-medium">{getProviderShowName(item)}</span>
<span className="text-text-tertiary">{item.tool_label}</span>
{!item.isDeleted && !readonly && (
<Tooltip
@@ -268,7 +268,7 @@ const AgentTools: FC = () => {
needsDelay={false}
>
<div
className="cursor-pointer rounded-md p-1 hover:bg-black/5"
className="cursor-pointer rounded-md p-1 hover:bg-black/5"
onClick={() => {
setCurrentTool(item)
setIsShowSettingTool(true)

View File

@@ -96,7 +96,7 @@ const Editor: FC<Props> = ({
)}
</div>
</div>
<div className={cn(editorHeight, ' min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}>
<div className={cn(editorHeight, 'min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}>
<PromptEditor
className={editorHeight}
value={value}

View File

@@ -29,10 +29,10 @@ const ConfigAudio: FC = () => {
const newFeatures = produce(features, (draft) => {
if (value) {
draft.file!.allowed_file_types = Array.from(new Set([
draft.file!.allowed_file_types = [...new Set([
...(draft.file?.allowed_file_types || []),
SupportUploadFileTypes.audio,
]))
])]
}
else {
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
@@ -56,7 +56,7 @@ const ConfigAudio: FC = () => {
</div>
</div>
<div className="flex grow items-center">
<div className="system-sm-semibold mr-1 text-text-secondary">{t('feature.audioUpload.title', { ns: 'appDebug' })}</div>
<div className="mr-1 text-text-secondary system-sm-semibold">{t('feature.audioUpload.title', { ns: 'appDebug' })}</div>
<Tooltip
popupContent={(
<div className="w-[180px]">

View File

@@ -29,10 +29,10 @@ const ConfigDocument: FC = () => {
const newFeatures = produce(features, (draft) => {
if (value) {
draft.file!.allowed_file_types = Array.from(new Set([
draft.file!.allowed_file_types = [...new Set([
...(draft.file?.allowed_file_types || []),
SupportUploadFileTypes.document,
]))
])]
}
else {
draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter(
@@ -56,7 +56,7 @@ const ConfigDocument: FC = () => {
</div>
</div>
<div className="flex grow items-center">
<div className="system-sm-semibold mr-1 text-text-secondary">{t('feature.documentUpload.title', { ns: 'appDebug' })}</div>
<div className="mr-1 text-text-secondary system-sm-semibold">{t('feature.documentUpload.title', { ns: 'appDebug' })}</div>
<Tooltip
popupContent={(
<div className="w-[180px]">

View File

@@ -210,7 +210,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div className="overflow-y-auto border-b border-divider-regular p-6 pb-[68px] pt-5">
<div className={cn(rowClass, 'items-center')}>
<div className={labelClass}>
<div className="system-sm-semibold text-text-secondary">{t('form.name', { ns: 'datasetSettings' })}</div>
<div className="text-text-secondary system-sm-semibold">{t('form.name', { ns: 'datasetSettings' })}</div>
</div>
<Input
value={localeCurrentDataset.name}
@@ -221,7 +221,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
</div>
<div className={cn(rowClass)}>
<div className={labelClass}>
<div className="system-sm-semibold text-text-secondary">{t('form.desc', { ns: 'datasetSettings' })}</div>
<div className="text-text-secondary system-sm-semibold">{t('form.desc', { ns: 'datasetSettings' })}</div>
</div>
<div className="w-full">
<Textarea
@@ -234,7 +234,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
</div>
<div className={rowClass}>
<div className={labelClass}>
<div className="system-sm-semibold text-text-secondary">{t('form.permissions', { ns: 'datasetSettings' })}</div>
<div className="text-text-secondary system-sm-semibold">{t('form.permissions', { ns: 'datasetSettings' })}</div>
</div>
<div className="w-full">
<PermissionSelector
@@ -250,7 +250,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
{!!(currentDataset && currentDataset.indexing_technique) && (
<div className={cn(rowClass)}>
<div className={labelClass}>
<div className="system-sm-semibold text-text-secondary">{t('form.indexMethod', { ns: 'datasetSettings' })}</div>
<div className="text-text-secondary system-sm-semibold">{t('form.indexMethod', { ns: 'datasetSettings' })}</div>
</div>
<div className="grow">
<IndexMethod
@@ -267,7 +267,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
{indexMethod === IndexingType.QUALIFIED && (
<div className={cn(rowClass)}>
<div className={labelClass}>
<div className="system-sm-semibold text-text-secondary">{t('form.embeddingModel', { ns: 'datasetSettings' })}</div>
<div className="text-text-secondary system-sm-semibold">{t('form.embeddingModel', { ns: 'datasetSettings' })}</div>
</div>
<div className="w-full">
<div className="h-8 w-full rounded-lg bg-components-input-bg-normal opacity-60">

View File

@@ -179,7 +179,7 @@ const Debug: FC<IDebug> = ({
return false
}
if (completionFiles.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
if (completionFiles.some(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
notify({ type: 'info', message: t('errorMessage.waitForFileUpload', { ns: 'appDebug' }) })
return false
}
@@ -394,7 +394,7 @@ const Debug: FC<IDebug> = ({
<>
<div className="shrink-0">
<div className="flex items-center justify-between px-4 pb-2 pt-3">
<div className="system-xl-semibold text-text-primary">{t('inputs.title', { ns: 'appDebug' })}</div>
<div className="text-text-primary system-xl-semibold">{t('inputs.title', { ns: 'appDebug' })}</div>
<div className="flex items-center">
{
debugWithMultipleModel
@@ -539,7 +539,7 @@ const Debug: FC<IDebug> = ({
{!completionRes && !isResponding && (
<div className="flex grow flex-col items-center justify-center gap-2">
<RiSparklingFill className="h-12 w-12 text-text-empty-state-icon" />
<div className="system-sm-regular text-text-quaternary">{t('noResult', { ns: 'appDebug' })}</div>
<div className="text-text-quaternary system-sm-regular">{t('noResult', { ns: 'appDebug' })}</div>
</div>
)}
</>

View File

@@ -263,7 +263,7 @@ const Configuration: FC = () => {
formattingChangedDispatcher()
let newDatasets = data
if (data.find(item => !item.name)) { // has not loaded selected dataset
if (data.some(item => !item.name)) { // has not loaded selected dataset
const newSelected = produce(data, (draft) => {
data.forEach((item, index) => {
if (!item.name) { // not fetched database
@@ -966,10 +966,10 @@ const Configuration: FC = () => {
<div className="bg-default-subtle absolute left-0 top-0 h-14 w-full">
<div className="flex h-14 items-center justify-between px-6">
<div className="flex items-center">
<div className="system-xl-semibold text-text-primary">{t('orchestrate', { ns: 'appDebug' })}</div>
<div className="text-text-primary system-xl-semibold">{t('orchestrate', { ns: 'appDebug' })}</div>
<div className="flex h-[14px] items-center space-x-1 text-xs">
{isAdvancedMode && (
<div className="system-xs-medium-uppercase ml-1 flex h-5 items-center rounded-md border border-components-button-secondary-border px-1.5 uppercase text-text-tertiary">{t('promptMode.advanced', { ns: 'appDebug' })}</div>
<div className="ml-1 flex h-5 items-center rounded-md border border-components-button-secondary-border px-1.5 uppercase text-text-tertiary system-xs-medium-uppercase">{t('promptMode.advanced', { ns: 'appDebug' })}</div>
)}
</div>
</div>
@@ -1030,8 +1030,8 @@ const Configuration: FC = () => {
<Config />
</div>
{!isMobile && (
<div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className="flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg ">
<div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className="flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg">
<Debug
isAPIKeySet={isAPIKeySet}
onSetting={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.PROVIDER })}

View File

@@ -217,7 +217,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
<AppIcon
size="large"
onClick={() => { setShowEmojiPicker(true) }}
className="!h-9 !w-9 cursor-pointer rounded-lg border-[0.5px] border-components-panel-border "
className="!h-9 !w-9 cursor-pointer rounded-lg border-[0.5px] border-components-panel-border"
icon={localeData.icon}
background={localeData.icon_background}
/>

View File

@@ -117,10 +117,10 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
<div className="px-10">
<div className="h-6 w-full 2xl:h-[139px]" />
<div className="pb-6 pt-1">
<span className="title-2xl-semi-bold text-text-primary">{t('newApp.startFromBlank', { ns: 'app' })}</span>
<span className="text-text-primary title-2xl-semi-bold">{t('newApp.startFromBlank', { ns: 'app' })}</span>
</div>
<div className="mb-2 leading-6">
<span className="system-sm-semibold text-text-secondary">{t('newApp.chooseAppType', { ns: 'app' })}</span>
<span className="text-text-secondary system-sm-semibold">{t('newApp.chooseAppType', { ns: 'app' })}</span>
</div>
<div className="flex w-[660px] flex-col gap-4">
<div>
@@ -160,7 +160,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
className="flex cursor-pointer items-center border-0 bg-transparent p-0"
onClick={() => setIsAppTypeExpanded(!isAppTypeExpanded)}
>
<span className="system-2xs-medium-uppercase text-text-tertiary">{t('newApp.forBeginners', { ns: 'app' })}</span>
<span className="text-text-tertiary system-2xs-medium-uppercase">{t('newApp.forBeginners', { ns: 'app' })}</span>
<RiArrowRightSLine className={`ml-1 h-4 w-4 text-text-tertiary transition-transform ${isAppTypeExpanded ? 'rotate-90' : ''}`} />
</button>
</div>
@@ -212,7 +212,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
<div className="flex items-center space-x-3">
<div className="flex-1">
<div className="mb-1 flex h-6 items-center">
<label className="system-sm-semibold text-text-secondary">{t('newApp.captionName', { ns: 'app' })}</label>
<label className="text-text-secondary system-sm-semibold">{t('newApp.captionName', { ns: 'app' })}</label>
</div>
<Input
value={name}
@@ -243,8 +243,8 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
</div>
<div>
<div className="mb-1 flex h-6 items-center">
<label className="system-sm-semibold text-text-secondary">{t('newApp.captionDescription', { ns: 'app' })}</label>
<span className="system-xs-regular ml-1 text-text-tertiary">
<label className="text-text-secondary system-sm-semibold">{t('newApp.captionDescription', { ns: 'app' })}</label>
<span className="ml-1 text-text-tertiary system-xs-regular">
(
{t('newApp.optional', { ns: 'app' })}
)
@@ -260,7 +260,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
</div>
{isAppsFull && <AppsFull className="mt-4" loc="app-create" />}
<div className="flex items-center justify-between pb-10 pt-5">
<div className="system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary" onClick={onCreateFromTemplate}>
<div className="flex cursor-pointer items-center gap-1 text-text-tertiary system-xs-regular" onClick={onCreateFromTemplate}>
<span>{t('newApp.noIdeaTip', { ns: 'app' })}</span>
<div className="p-[1px]">
<RiArrowRightLine className="h-3.5 w-3.5" />
@@ -334,8 +334,8 @@ function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardP
onClick={onClick}
>
{icon}
<div className="system-sm-semibold mb-0.5 mt-2 text-text-secondary">{title}</div>
<div className="system-xs-regular line-clamp-2 text-text-tertiary" title={description}>{description}</div>
<div className="mb-0.5 mt-2 text-text-secondary system-sm-semibold">{title}</div>
<div className="line-clamp-2 text-text-tertiary system-xs-regular" title={description}>{description}</div>
</div>
)
}
@@ -367,8 +367,8 @@ function AppPreview({ mode }: { mode: AppModeEnum }) {
const previewInfo = modeToPreviewInfoMap[mode]
return (
<div className="px-8 py-4">
<h4 className="system-sm-semibold-uppercase text-text-secondary">{previewInfo.title}</h4>
<div className="system-xs-regular mt-1 min-h-8 max-w-96 text-text-tertiary">
<h4 className="text-text-secondary system-sm-semibold-uppercase">{previewInfo.title}</h4>
<div className="mt-1 min-h-8 max-w-96 text-text-tertiary system-xs-regular">
<span>{previewInfo.description}</span>
</div>
</div>

View File

@@ -232,7 +232,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
isShow={show}
onClose={noop}
>
<div className="title-2xl-semi-bold flex items-center justify-between pb-3 pl-6 pr-5 pt-6 text-text-primary">
<div className="flex items-center justify-between pb-3 pl-6 pr-5 pt-6 text-text-primary title-2xl-semi-bold">
{t('importFromDSL', { ns: 'app' })}
<div
className="flex h-8 w-8 cursor-pointer items-center"
@@ -241,7 +241,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
</div>
</div>
<div className="system-md-semibold flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 text-text-tertiary">
<div className="flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 text-text-tertiary system-md-semibold">
{
tabs.map(tab => (
<div
@@ -275,7 +275,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
{
currentTab === CreateFromDSLModalTab.FROM_URL && (
<div>
<div className="system-md-semibold mb-1 text-text-secondary">DSL URL</div>
<div className="mb-1 text-text-secondary system-md-semibold">DSL URL</div>
<Input
placeholder={t('importFromDSLUrlPlaceholder', { ns: 'app' }) || ''}
value={dslUrlValue}
@@ -309,8 +309,8 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
className="w-[480px]"
>
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
<div className="system-md-regular flex grow flex-col text-text-secondary">
<div className="text-text-primary title-2xl-semi-bold">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
<div className="flex grow flex-col text-text-secondary system-md-regular">
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
<br />

View File

@@ -58,7 +58,7 @@ const Uploader: FC<Props> = ({
setDragging(false)
if (!e.dataTransfer)
return
const files = Array.from(e.dataTransfer.files)
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('stepOne.uploader.validation.count', { ns: 'datasetCreation' }) })
return
@@ -121,7 +121,7 @@ const Uploader: FC<Props> = ({
</div>
)}
{file && (
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', 'hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className="flex items-center justify-center p-3">
<YamlIcon className="h-6 w-6 shrink-0" />
</div>

View File

@@ -96,7 +96,7 @@ const statusTdRender = (statusCount: StatusCount) => {
if (statusCount.paused > 0) {
return (
<div className="system-xs-semibold-uppercase inline-flex items-center gap-1">
<div className="inline-flex items-center gap-1 system-xs-semibold-uppercase">
<Indicator color="yellow" />
<span className="text-util-colors-warning-warning-600">Pending</span>
</div>
@@ -104,7 +104,7 @@ const statusTdRender = (statusCount: StatusCount) => {
}
else if (statusCount.partial_success + statusCount.failed === 0) {
return (
<div className="system-xs-semibold-uppercase inline-flex items-center gap-1">
<div className="inline-flex items-center gap-1 system-xs-semibold-uppercase">
<Indicator color="green" />
<span className="text-util-colors-green-green-600">Success</span>
</div>
@@ -112,7 +112,7 @@ const statusTdRender = (statusCount: StatusCount) => {
}
else if (statusCount.failed === 0) {
return (
<div className="system-xs-semibold-uppercase inline-flex items-center gap-1">
<div className="inline-flex items-center gap-1 system-xs-semibold-uppercase">
<Indicator color="green" />
<span className="text-util-colors-green-green-600">Partial Success</span>
</div>
@@ -120,7 +120,7 @@ const statusTdRender = (statusCount: StatusCount) => {
}
else {
return (
<div className="system-xs-semibold-uppercase inline-flex items-center gap-1">
<div className="inline-flex items-center gap-1 system-xs-semibold-uppercase">
<Indicator color="red" />
<span className="text-util-colors-red-red-600">
{statusCount.failed}
@@ -157,7 +157,7 @@ const getFormattedChatList = (messages: ChatMessage[], conversationId: string, t
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))),
log: [
...(item.message ?? []),
...(item.message?.[item.message.length - 1]?.role !== 'assistant'
...(item.message.at(-1)?.role !== 'assistant'
? [
{
role: 'assistant',
@@ -325,7 +325,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
}
setChatItemTree(tree)
const lastMessageId = allChatItems.length > 0 ? allChatItems[allChatItems.length - 1].id : undefined
const lastMessageId = allChatItems.length > 0 ? allChatItems.at(-1).id : undefined
setThreadChatItems(getThreadMessages(tree, lastMessageId))
// Update pagination anchor ref with the oldest answer ID
@@ -562,9 +562,9 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
{/* Panel Header */}
<div className="flex shrink-0 items-center gap-2 rounded-t-xl bg-components-panel-bg pb-2 pl-4 pr-3 pt-3">
<div className="shrink-0">
<div className="system-xs-semibold-uppercase mb-0.5 text-text-primary">{isChatMode ? t('detail.conversationId', { ns: 'appLog' }) : t('detail.time', { ns: 'appLog' })}</div>
<div className="mb-0.5 text-text-primary system-xs-semibold-uppercase">{isChatMode ? t('detail.conversationId', { ns: 'appLog' }) : t('detail.time', { ns: 'appLog' })}</div>
{isChatMode && (
<div className="system-2xs-regular-uppercase flex items-center text-text-secondary">
<div className="flex items-center text-text-secondary system-2xs-regular-uppercase">
<Tooltip
popupContent={detail.id}
>
@@ -574,7 +574,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
</div>
)}
{!isChatMode && (
<div className="system-2xs-regular-uppercase text-text-secondary">{formatTime(detail.created_at, t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
<div className="text-text-secondary system-2xs-regular-uppercase">{formatTime(detail.created_at, t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
)}
</div>
<div className="flex grow flex-wrap items-center justify-end gap-y-1">
@@ -600,7 +600,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
? (
<div className="px-6 py-4">
<div className="flex h-[18px] items-center space-x-3">
<div className="system-xs-semibold-uppercase text-text-tertiary">{t('table.header.output', { ns: 'appLog' })}</div>
<div className="text-text-tertiary system-xs-semibold-uppercase">{t('table.header.output', { ns: 'appLog' })}</div>
<div
className="h-px grow"
style={{
@@ -692,7 +692,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
</div>
{hasMore && (
<div className="py-3 text-center">
<div className="system-xs-regular text-text-tertiary">
<div className="text-text-tertiary system-xs-regular">
{t('detail.loading', { ns: 'appLog' })}
...
</div>
@@ -950,7 +950,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
)}
popupClassName={(isHighlight && !isChatMode) ? '' : '!hidden'}
>
<div className={cn(isEmptyStyle ? 'text-text-quaternary' : 'text-text-secondary', !isHighlight ? '' : 'bg-orange-100', 'system-sm-regular overflow-hidden text-ellipsis whitespace-nowrap')}>
<div className={cn(isEmptyStyle ? 'text-text-quaternary' : 'text-text-secondary', !isHighlight ? '' : 'bg-orange-100', 'overflow-hidden text-ellipsis whitespace-nowrap system-sm-regular')}>
{value || '-'}
</div>
</Tooltip>
@@ -963,7 +963,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
return (
<div className="relative mt-2 grow overflow-x-auto">
<table className={cn('w-full min-w-[440px] border-collapse border-0')}>
<thead className="system-xs-medium-uppercase text-text-tertiary">
<thead className="text-text-tertiary system-xs-medium-uppercase">
<tr>
<td className="w-5 whitespace-nowrap rounded-l-lg bg-background-section-burn pl-2 pr-1"></td>
<td className="whitespace-nowrap bg-background-section-burn py-1.5 pl-3">{isChatMode ? t('table.header.summary', { ns: 'appLog' }) : t('table.header.input', { ns: 'appLog' })}</td>
@@ -976,7 +976,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
<td className="whitespace-nowrap rounded-r-lg bg-background-section-burn py-1.5 pl-3">{t('table.header.time', { ns: 'appLog' })}</td>
</tr>
</thead>
<tbody className="system-sm-regular text-text-secondary">
<tbody className="text-text-secondary system-sm-regular">
{logs.data.map((log: any) => {
const endUser = log.from_end_user_session_id || log.from_account_name
const leftValue = get(log, isChatMode ? 'name' : 'message.inputs.query') || (!isChatMode ? (get(log, 'message.query') || get(log, 'message.inputs.default_input')) : '') || ''

View File

@@ -266,7 +266,7 @@ function AppCard({
</div>
{!isMinimalState && (
<div className="flex flex-col items-start justify-center self-stretch">
<div className="system-xs-medium pb-1 text-text-tertiary">
<div className="pb-1 text-text-tertiary system-xs-medium">
{isApp
? t('overview.appInfo.accessibleAddress', { ns: 'appOverview' })
: t('overview.apiInfo.accessibleAddress', { ns: 'appOverview' })}
@@ -319,9 +319,9 @@ function AppCard({
)}
{!isMinimalState && isApp && systemFeatures.webapp_auth.enabled && appDetail && (
<div className="flex flex-col items-start justify-center self-stretch">
<div className="system-xs-medium pb-1 text-text-tertiary">{t('publishApp.title', { ns: 'app' })}</div>
<div className="pb-1 text-text-tertiary system-xs-medium">{t('publishApp.title', { ns: 'app' })}</div>
<div
className="flex h-9 w-full cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2"
className="flex h-9 w-full cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2"
onClick={handleClickAccessControl}
>
<div className="flex grow items-center gap-x-1.5 pr-1">
@@ -329,32 +329,32 @@ function AppCard({
&& (
<>
<RiBuildingLine className="h-4 w-4 shrink-0 text-text-secondary" />
<p className="system-sm-medium text-text-secondary">{t('accessControlDialog.accessItems.organization', { ns: 'app' })}</p>
<p className="text-text-secondary system-sm-medium">{t('accessControlDialog.accessItems.organization', { ns: 'app' })}</p>
</>
)}
{appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS
&& (
<>
<RiLockLine className="h-4 w-4 shrink-0 text-text-secondary" />
<p className="system-sm-medium text-text-secondary">{t('accessControlDialog.accessItems.specific', { ns: 'app' })}</p>
<p className="text-text-secondary system-sm-medium">{t('accessControlDialog.accessItems.specific', { ns: 'app' })}</p>
</>
)}
{appDetail?.access_mode === AccessMode.PUBLIC
&& (
<>
<RiGlobalLine className="h-4 w-4 shrink-0 text-text-secondary" />
<p className="system-sm-medium text-text-secondary">{t('accessControlDialog.accessItems.anyone', { ns: 'app' })}</p>
<p className="text-text-secondary system-sm-medium">{t('accessControlDialog.accessItems.anyone', { ns: 'app' })}</p>
</>
)}
{appDetail?.access_mode === AccessMode.EXTERNAL_MEMBERS
&& (
<>
<RiVerifiedBadgeLine className="h-4 w-4 shrink-0 text-text-secondary" />
<p className="system-sm-medium text-text-secondary">{t('accessControlDialog.accessItems.external', { ns: 'app' })}</p>
<p className="text-text-secondary system-sm-medium">{t('accessControlDialog.accessItems.external', { ns: 'app' })}</p>
</>
)}
</div>
{!isAppAccessSet && <p className="system-xs-regular shrink-0 text-text-tertiary">{t('publishApp.notSet', { ns: 'app' })}</p>}
{!isAppAccessSet && <p className="shrink-0 text-text-tertiary system-xs-regular">{t('publishApp.notSet', { ns: 'app' })}</p>}
<div className="flex h-4 w-4 shrink-0 items-center justify-center">
<RiArrowRightSLine className="h-4 w-4 text-text-quaternary" />
</div>
@@ -389,7 +389,7 @@ function AppCard({
>
<div className="flex items-center justify-center gap-[1px]">
<op.opIcon className="h-3.5 w-3.5" />
<div className={`${(runningStatus || !disabled) ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} system-xs-medium px-[3px]`}>{op.opName}</div>
<div className={`${(runningStatus || !disabled) ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} px-[3px] system-xs-medium`}>{op.opName}</div>
</div>
</Tooltip>
</Button>

View File

@@ -231,12 +231,12 @@ const SettingsModal: FC<ISettingsModalProps> = ({
{/* header */}
<div className="pb-3 pl-6 pr-5 pt-5">
<div className="flex items-center gap-1">
<div className="title-2xl-semi-bold grow text-text-primary">{t(`${prefixSettings}.title`, { ns: 'appOverview' })}</div>
<div className="grow text-text-primary title-2xl-semi-bold">{t(`${prefixSettings}.title`, { ns: 'appOverview' })}</div>
<ActionButton className="shrink-0" onClick={onHide}>
<RiCloseLine className="h-4 w-4" />
</ActionButton>
</div>
<div className="system-xs-regular mt-0.5 text-text-tertiary">
<div className="mt-0.5 text-text-tertiary system-xs-regular">
<span>{t(`${prefixSettings}.modalTip`, { ns: 'appOverview' })}</span>
</div>
</div>
@@ -245,7 +245,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
{/* name & icon */}
<div className="flex gap-4">
<div className="grow">
<div className={cn('system-sm-semibold mb-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}</div>
<div className={cn('mb-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}</div>
<Input
className="w-full"
value={inputInfo.title}
@@ -265,32 +265,32 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
{/* description */}
<div className="relative">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}</div>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}</div>
<Textarea
className="mt-1"
value={inputInfo.desc}
onChange={e => onDesChange(e.target.value)}
placeholder={t(`${prefixSettings}.webDescPlaceholder`, { ns: 'appOverview' }) as string}
/>
<p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.webDescTip`, { ns: 'appOverview' })}</p>
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.webDescTip`, { ns: 'appOverview' })}</p>
</div>
<Divider className="my-0 h-px" />
{/* answer icon */}
{isChat && (
<div className="w-full">
<div className="flex items-center justify-between">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t('answerIcon.title', { ns: 'app' })}</div>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t('answerIcon.title', { ns: 'app' })}</div>
<Switch
value={inputInfo.use_icon_as_answer_icon}
onChange={v => setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
/>
</div>
<p className="body-xs-regular pb-0.5 text-text-tertiary">{t('answerIcon.description', { ns: 'app' })}</p>
<p className="pb-0.5 text-text-tertiary body-xs-regular">{t('answerIcon.description', { ns: 'app' })}</p>
</div>
)}
{/* language */}
<div className="flex items-center">
<div className={cn('system-sm-semibold grow py-1 text-text-secondary')}>{t(`${prefixSettings}.language`, { ns: 'appOverview' })}</div>
<div className={cn('grow py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.language`, { ns: 'appOverview' })}</div>
<SimpleSelect
wrapperClassName="w-[200px]"
items={languages.filter(item => item.supported)}
@@ -303,8 +303,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
{isChat && (
<div className="flex items-center">
<div className="grow">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.chatColorTheme`, { ns: 'appOverview' })}</div>
<div className="body-xs-regular pb-0.5 text-text-tertiary">{t(`${prefixSettings}.chatColorThemeDesc`, { ns: 'appOverview' })}</div>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.chatColorTheme`, { ns: 'appOverview' })}</div>
<div className="pb-0.5 text-text-tertiary body-xs-regular">{t(`${prefixSettings}.chatColorThemeDesc`, { ns: 'appOverview' })}</div>
</div>
<div className="shrink-0">
<Input
@@ -314,7 +314,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
placeholder="E.g #A020F0"
/>
<div className="flex items-center justify-between">
<p className={cn('body-xs-regular text-text-tertiary')}>{t(`${prefixSettings}.chatColorThemeInverted`, { ns: 'appOverview' })}</p>
<p className={cn('text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.chatColorThemeInverted`, { ns: 'appOverview' })}</p>
<Switch value={inputInfo.chatColorThemeInverted} onChange={v => setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}></Switch>
</div>
</div>
@@ -323,22 +323,22 @@ const SettingsModal: FC<ISettingsModalProps> = ({
{/* workflow detail */}
<div className="w-full">
<div className="flex items-center justify-between">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.workflow.subTitle`, { ns: 'appOverview' })}</div>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.workflow.subTitle`, { ns: 'appOverview' })}</div>
<Switch
disabled={!(appInfo.mode === AppModeEnum.WORKFLOW || appInfo.mode === AppModeEnum.ADVANCED_CHAT)}
value={inputInfo.show_workflow_steps}
onChange={v => setInputInfo({ ...inputInfo, show_workflow_steps: v })}
/>
</div>
<p className="body-xs-regular pb-0.5 text-text-tertiary">{t(`${prefixSettings}.workflow.showDesc`, { ns: 'appOverview' })}</p>
<p className="pb-0.5 text-text-tertiary body-xs-regular">{t(`${prefixSettings}.workflow.showDesc`, { ns: 'appOverview' })}</p>
</div>
{/* more settings switch */}
<Divider className="my-0 h-px" />
{!isShowMore && (
<div className="flex cursor-pointer items-center" onClick={() => setIsShowMore(true)}>
<div className="grow">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.entry`, { ns: 'appOverview' })}</div>
<p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.entry`, { ns: 'appOverview' })}</div>
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>
{t(`${prefixSettings}.more.copyRightPlaceholder`, { ns: 'appOverview' })}
{' '}
&
@@ -356,7 +356,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
<div className="w-full">
<div className="flex items-center">
<div className="flex grow items-center">
<div className={cn('system-sm-semibold mr-1 py-1 text-text-secondary')}>{t(`${prefixSettings}.more.copyright`, { ns: 'appOverview' })}</div>
<div className={cn('mr-1 py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.copyright`, { ns: 'appOverview' })}</div>
{/* upgrade button */}
{enableBilling && isFreePlan && (
<div className="h-[18px] select-none">
@@ -385,7 +385,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
/>
</Tooltip>
</div>
<p className="body-xs-regular pb-0.5 text-text-tertiary">{t(`${prefixSettings}.more.copyrightTip`, { ns: 'appOverview' })}</p>
<p className="pb-0.5 text-text-tertiary body-xs-regular">{t(`${prefixSettings}.more.copyrightTip`, { ns: 'appOverview' })}</p>
{inputInfo.copyrightSwitchValue && (
<Input
className="mt-2 h-10"
@@ -397,8 +397,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
{/* privacy policy */}
<div className="w-full">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.privacyPolicy`, { ns: 'appOverview' })}</div>
<p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.privacyPolicy`, { ns: 'appOverview' })}</div>
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>
<Trans
i18nKey={`${prefixSettings}.more.privacyPolicyTip`}
ns="appOverview"
@@ -414,8 +414,8 @@ const SettingsModal: FC<ISettingsModalProps> = ({
</div>
{/* custom disclaimer */}
<div className="w-full">
<div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}</div>
<p className={cn('body-xs-regular pb-0.5 text-text-tertiary')}>{t(`${prefixSettings}.more.customDisclaimerTip`, { ns: 'appOverview' })}</p>
<div className={cn('py-1 text-text-secondary system-sm-semibold')}>{t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}</div>
<p className={cn('pb-0.5 text-text-tertiary body-xs-regular')}>{t(`${prefixSettings}.more.customDisclaimerTip`, { ns: 'appOverview' })}</p>
<Textarea
className="mt-1"
value={inputInfo.customDisclaimer}

View File

@@ -161,7 +161,7 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
<TriggerAll className="h-4 w-4 text-text-primary-on-surface" />
</div>
<div className="group w-full">
<div className="system-md-semibold min-w-0 overflow-hidden text-ellipsis break-normal text-text-secondary group-hover:text-text-primary">
<div className="min-w-0 overflow-hidden text-ellipsis break-normal text-text-secondary system-md-semibold group-hover:text-text-primary">
{triggerCount > 0
? t('overview.triggerInfo.triggersAdded', { ns: 'appOverview', count: triggerCount })
: t('overview.triggerInfo.noTriggerAdded', { ns: 'appOverview' })}
@@ -179,12 +179,12 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
<div className="shrink-0">
{getTriggerIcon(trigger, triggerPlugins || [])}
</div>
<div className="system-sm-medium min-w-0 flex-1 truncate text-text-secondary">
<div className="min-w-0 flex-1 truncate text-text-secondary system-sm-medium">
{trigger.title}
</div>
</div>
<div className="flex shrink-0 items-center">
<div className={`${trigger.status === 'enabled' ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase whitespace-nowrap`}>
<div className={`${trigger.status === 'enabled' ? 'text-text-success' : 'text-text-warning'} whitespace-nowrap system-xs-semibold-uppercase`}>
{trigger.status === 'enabled'
? t('overview.status.running', { ns: 'appOverview' })
: t('overview.status.disable', { ns: 'appOverview' })}
@@ -204,7 +204,7 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
{triggerCount === 0 && (
<div className="p-3">
<div className="system-xs-regular leading-4 text-text-tertiary">
<div className="leading-4 text-text-tertiary system-xs-regular">
{t('overview.triggerInfo.triggerStatusDescription', { ns: 'appOverview' })}
{' '}
<Link

View File

@@ -277,7 +277,7 @@ describe('List', () => {
fireEvent.click(screen.getByText('app.types.workflow'))
await vi.waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const lastCall = onUrlUpdate.mock.calls.at(-1)[0]
expect(lastCall.searchParams.get('category')).toBe(AppModeEnum.WORKFLOW)
})
@@ -287,7 +287,7 @@ describe('List', () => {
fireEvent.click(screen.getByText('app.types.all'))
await vi.waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const lastCall = onUrlUpdate.mock.calls.at(-1)[0]
// nuqs removes the default value ('all') from URL params
expect(lastCall.searchParams.has('category')).toBe(false)
})
@@ -449,7 +449,7 @@ describe('List', () => {
onUrlUpdate.mockClear()
fireEvent.click(screen.getByText(text))
await vi.waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const lastCall = onUrlUpdate.mock.calls.at(-1)[0]
expect(lastCall.searchParams.get('category')).toBe(mode)
}
})

View File

@@ -114,7 +114,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('keywords')).toBe('search')
expect(update.options.history).toBe('push')
})
@@ -127,7 +127,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('tagIDs')).toBe('tag1;tag2')
})
@@ -139,7 +139,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('isCreatedByMe')).toBe('true')
})
@@ -151,7 +151,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('keywords')).toBe(false)
})
@@ -163,7 +163,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('tagIDs')).toBe(false)
})
@@ -175,7 +175,7 @@ describe('useAppsQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('isCreatedByMe')).toBe(false)
})
})

View File

@@ -36,7 +36,7 @@ describe('AudioBtn', () => {
}
const getLatestAudioCallback = () => {
const lastCall = mockGetAudioPlayer.mock.calls[mockGetAudioPlayer.mock.calls.length - 1]
const lastCall = mockGetAudioPlayer.mock.calls.at(-1)
const callback = lastCall?.[5]
if (typeof callback !== 'function')

View File

@@ -65,7 +65,7 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({ src, srcs }) => {
if (primarySrc) {
// Delayed generation of waveform data
// eslint-disable-next-line ts/no-use-before-define
const timer = setTimeout(() => generateWaveformData(primarySrc), 1000)
const timer = setTimeout(generateWaveformData, 1000, primarySrc)
return () => {
audio.removeEventListener('loadedmetadata', setAudioData)
audio.removeEventListener('timeupdate', setAudioTime)

View File

@@ -267,7 +267,7 @@ describe('AudioPlayer — waveform generation', () => {
render(<AudioPlayer src="https://example.com/audio.mp3" />)
await advanceWaveformTimer()
const toastFound = Array.from(document.body.querySelectorAll('div')).some(
const toastFound = [...document.body.querySelectorAll('div')].some(
d => d.textContent?.includes('Web Audio API is not supported in this browser'),
)
expect(toastFound).toBe(true)

View File

@@ -40,7 +40,7 @@ const createMockEmblaApi = (): MockEmblaApi => ({
canScrollPrev: vi.fn(() => mockCanScrollPrev),
canScrollNext: vi.fn(() => mockCanScrollNext),
slideNodes: vi.fn(() =>
Array.from({ length: mockSlideCount }, () => document.createElement('div')),
Array.from({ length: mockSlideCount }).fill(document.createElement('div')),
),
on: vi.fn((event: EmblaEventName, callback: EmblaListener) => {
listeners[event].push(callback)

View File

@@ -319,7 +319,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const disabledContainer = chatInput.closest('.pointer-events-none')
expect(disabledContainer).toBeInTheDocument()
expect(disabledContainer).toHaveClass('opacity-50')
@@ -336,7 +336,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).not.toBeInTheDocument()
})
@@ -360,7 +360,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).toBeInTheDocument()
})
@@ -410,7 +410,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).toBeInTheDocument()
})
@@ -1101,7 +1101,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).toBeInTheDocument()
})
@@ -1242,7 +1242,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
// This tests line 106 - early return when hasEmptyInput is set
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).toBeInTheDocument()
})
@@ -1270,7 +1270,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
// This tests line 109 - early return when fileIsUploading is set
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
expect(container).toBeInTheDocument()
})
@@ -1809,7 +1809,7 @@ describe('ChatWrapper', () => {
render(<ChatWrapper />)
const textboxes = screen.getAllByRole('textbox')
const chatInput = textboxes[textboxes.length - 1]
const chatInput = textboxes.at(-1)
const container = chatInput.closest('.pointer-events-none')
// Should not be disabled because it's not required
expect(container).not.toBeInTheDocument()

View File

@@ -132,7 +132,7 @@ describe('Header Component', () => {
const buttons = screen.getAllByRole('button')
// Sidebar, NewChat, ResetChat (3)
const resetChatBtn = buttons[buttons.length - 1]
const resetChatBtn = buttons.at(-1)
await userEvent.click(resetChatBtn)
expect(handleNewConversation).toHaveBeenCalled()

View File

@@ -483,7 +483,7 @@ describe('useChat', () => {
callbacks.onNodeFinished({ data: { id: 'n-1', iteration_id: 'iter-1' } })
})
const traceLen1 = result.current.chatList[result.current.chatList.length - 1].workflowProcess?.tracing?.length
const traceLen1 = result.current.chatList.at(-1).workflowProcess?.tracing?.length
expect(traceLen1).toBe(0) // None added due to iteration early hits
})
@@ -567,7 +567,7 @@ describe('useChat', () => {
expect(result.current.chatList.some(item => item.id === 'question-m-child')).toBe(true)
expect(result.current.chatList.some(item => item.id === 'm-child')).toBe(true)
expect(result.current.chatList[result.current.chatList.length - 1].content).toBe('child answer')
expect(result.current.chatList.at(-1).content).toBe('child answer')
})
it('should strip local file urls before sending payload', () => {
@@ -665,7 +665,7 @@ describe('useChat', () => {
})
expect(onGetConversationMessages).toHaveBeenCalled()
expect(result.current.chatList[result.current.chatList.length - 1].content).toBe('streamed content')
expect(result.current.chatList.at(-1).content).toBe('streamed content')
})
it('should clear suggested questions when suggestion fetch fails after completion', async () => {
@@ -711,7 +711,7 @@ describe('useChat', () => {
callbacks.onNodeFinished({ data: { node_id: 'n-loop', id: 'n-loop' } })
})
const latestResponse = result.current.chatList[result.current.chatList.length - 1]
const latestResponse = result.current.chatList.at(-1)
expect(latestResponse.workflowProcess?.tracing).toHaveLength(0)
})
@@ -738,7 +738,7 @@ describe('useChat', () => {
callbacks.onTTSChunk('m-th-bind', '')
})
const latestResponse = result.current.chatList[result.current.chatList.length - 1]
const latestResponse = result.current.chatList.at(-1)
expect(latestResponse.id).toBe('m-th-bind')
expect(latestResponse.conversationId).toBe('c-th-bind')
expect(latestResponse.workflowProcess?.status).toBe('succeeded')
@@ -831,7 +831,7 @@ describe('useChat', () => {
callbacks.onCompleted()
})
const lastResponse = result.current.chatList[result.current.chatList.length - 1]
const lastResponse = result.current.chatList.at(-1)
expect(lastResponse.agent_thoughts![0].thought).toContain('resumed')
expect(lastResponse.workflowProcess?.tracing?.length).toBeGreaterThan(0)

View File

@@ -458,7 +458,7 @@ describe('Operation', () => {
const user = userEvent.setup()
renderOperation()
const thumbs = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-up-line')
const adminThumb = thumbs[thumbs.length - 1].closest('button')!
const adminThumb = thumbs.at(-1).closest('button')!
await user.click(adminThumb)
expect(mockContextValue.onFeedback).toHaveBeenCalledWith('msg-1', { rating: 'like', content: undefined })
})
@@ -467,7 +467,7 @@ describe('Operation', () => {
const user = userEvent.setup()
renderOperation()
const thumbs = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-down-line')
const adminThumb = thumbs[thumbs.length - 1].closest('button')!
const adminThumb = thumbs.at(-1).closest('button')!
await user.click(adminThumb)
expect(screen.getByRole('textbox')).toBeInTheDocument()
})
@@ -508,12 +508,12 @@ describe('Operation', () => {
const user = userEvent.setup()
renderOperation()
const thumbs = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-up-line')
const adminThumb = thumbs[thumbs.length - 1].closest('button')!
const adminThumb = thumbs.at(-1).closest('button')!
await user.click(adminThumb)
expect(mockContextValue.onFeedback).toHaveBeenCalledWith('msg-1', { rating: 'like', content: undefined })
const thumbsUndo = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-up-line')
const adminThumbUndo = thumbsUndo[thumbsUndo.length - 1].closest('button')!
const adminThumbUndo = thumbsUndo.at(-1).closest('button')!
await user.click(adminThumbUndo)
expect(mockContextValue.onFeedback).toHaveBeenCalledWith('msg-1', { rating: null, content: undefined })
})
@@ -522,13 +522,13 @@ describe('Operation', () => {
const user = userEvent.setup()
renderOperation()
const thumbs = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-down-line')
const adminThumb = thumbs[thumbs.length - 1].closest('button')!
const adminThumb = thumbs.at(-1).closest('button')!
await user.click(adminThumb)
const submitBtn = screen.getByText(/submit/i)
await user.click(submitBtn)
const thumbsUndo = screen.getByTestId('operation-bar').querySelectorAll('.i-ri-thumb-down-line')
const adminThumbUndo = thumbsUndo[thumbsUndo.length - 1].closest('button')!
const adminThumbUndo = thumbsUndo.at(-1).closest('button')!
await user.click(adminThumbUndo)
expect(mockContextValue.onFeedback).toHaveBeenCalledWith('msg-1', { rating: null, content: undefined })
})

View File

@@ -30,7 +30,7 @@ const WorkflowProcessItem = ({
const succeeded = data.status === WorkflowRunningStatus.Succeeded
const failed = data.status === WorkflowRunningStatus.Failed || data.status === WorkflowRunningStatus.Stopped
const paused = data.status === WorkflowRunningStatus.Paused
const latestNode = data.tracing[data.tracing.length - 1]
const latestNode = data.tracing.at(-1)
useEffect(() => {
setCollapse(!expand)

View File

@@ -168,7 +168,7 @@ describe('Chip', () => {
// Click on an item in the dropdown panel
const activeItems = screen.getAllByText('Active')
// The second one should be in the dropdown
fireEvent.click(activeItems[activeItems.length - 1])
fireEvent.click(activeItems.at(-1))
expect(trigger).toHaveAttribute('data-state', 'closed')
})
@@ -181,7 +181,7 @@ describe('Chip', () => {
openPanel(container)
// Get all "Active" texts and click the one in the dropdown (should be the last one)
const activeItems = screen.getAllByText('Active')
fireEvent.click(activeItems[activeItems.length - 1])
fireEvent.click(activeItems.at(-1))
expect(onSelect).toHaveBeenCalledTimes(1)
expect(onSelect).toHaveBeenCalledWith(items[1])
@@ -194,7 +194,7 @@ describe('Chip', () => {
const trigger = getTrigger(container)
const svgs = trigger?.querySelectorAll('svg')
// The close icon should be the last SVG element
const closeIcon = svgs?.[svgs.length - 1]
const closeIcon = svgs.at(-1)
const clearButton = closeIcon?.parentElement
expect(clearButton).toBeInTheDocument()
@@ -212,7 +212,7 @@ describe('Chip', () => {
// Find the close icon (last SVG) and click its parent
const svgs = trigger?.querySelectorAll('svg')
const closeIcon = svgs?.[svgs.length - 1]
const closeIcon = svgs.at(-1)
const clearButton = closeIcon?.parentElement
if (clearButton)
@@ -299,7 +299,7 @@ describe('Chip', () => {
// Find the dropdown panel items
const allActiveTexts = screen.getAllByText('Active')
// The dropdown item should be the last one
const dropdownItem = allActiveTexts[allActiveTexts.length - 1]
const dropdownItem = allActiveTexts.at(-1)
const parentContainer = dropdownItem.parentElement
// The check icon should be a sibling within the parent
@@ -346,7 +346,7 @@ describe('Chip', () => {
// Click on the already selected item in the dropdown
const activeItems = screen.getAllByText('Active')
fireEvent.click(activeItems[activeItems.length - 1])
fireEvent.click(activeItems.at(-1))
expect(onSelect).toHaveBeenCalledTimes(1)
expect(onSelect).toHaveBeenCalledWith(items[1])
@@ -367,7 +367,7 @@ describe('Chip', () => {
openPanel(container)
const thirdItems = screen.getAllByText('Third')
fireEvent.click(thirdItems[thirdItems.length - 1])
fireEvent.click(thirdItems.at(-1))
expect(onSelect).toHaveBeenCalledWith(numericItems[2])
})
@@ -386,7 +386,7 @@ describe('Chip', () => {
openPanel(container)
const itemBs = screen.getAllByText('Item B')
fireEvent.click(itemBs[itemBs.length - 1])
fireEvent.click(itemBs.at(-1))
expect(onSelect).toHaveBeenCalledWith(itemsWithExtra[1])
})

View File

@@ -85,7 +85,7 @@ function Confirm({
setIsVisible(true)
}
else {
const timer = setTimeout(() => setIsVisible(false), 200)
const timer = setTimeout(setIsVisible, 200, false)
return () => clearTimeout(timer)
}
}, [isShow])
@@ -119,11 +119,11 @@ function Confirm({
asChild={false}
triggerClassName="w-full"
>
<div ref={titleRef} className="title-2xl-semi-bold w-full truncate text-text-primary">
<div ref={titleRef} className="w-full truncate text-text-primary title-2xl-semi-bold">
{title}
</div>
</Tooltip>
<div className="system-md-regular w-full whitespace-pre-wrap break-words text-text-tertiary">{content}</div>
<div className="w-full whitespace-pre-wrap break-words text-text-tertiary system-md-regular">{content}</div>
</div>
<div className="flex items-start justify-end gap-2 self-stretch p-6">
{showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}

View File

@@ -435,7 +435,7 @@ describe('DatePicker', () => {
// Confirm - click the last OK button (year/month footer)
const okButtons = screen.getAllByText(/operation\.ok/)
fireEvent.click(okButtons[okButtons.length - 1])
fireEvent.click(okButtons.at(-1))
// Should return to date view
expect(screen.getAllByText(/daysInWeek/).length).toBeGreaterThan(0)
@@ -472,7 +472,7 @@ describe('DatePicker', () => {
// Confirm the selection - click the last OK button (year/month footer)
const okButtons = screen.getAllByText(/operation\.ok/)
fireEvent.click(okButtons[okButtons.length - 1])
fireEvent.click(okButtons.at(-1))
// Should return to date view
expect(screen.getAllByText(/daysInWeek/).length).toBeGreaterThan(0)

View File

@@ -92,20 +92,20 @@ const AnnotationReply = ({
>
<>
{!annotationReply?.enabled && (
<div className="system-xs-regular line-clamp-2 min-h-8 text-text-tertiary">{t('feature.annotation.description', { ns: 'appDebug' })}</div>
<div className="line-clamp-2 min-h-8 text-text-tertiary system-xs-regular">{t('feature.annotation.description', { ns: 'appDebug' })}</div>
)}
{!!annotationReply?.enabled && (
<>
{!isHovering && (
<div className="flex items-center gap-4 pt-0.5">
<div className="">
<div className="system-2xs-medium-uppercase mb-0.5 text-text-tertiary">{t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })}</div>
<div className="system-xs-regular text-text-secondary">{annotationReply.score_threshold || '-'}</div>
<div className="mb-0.5 text-text-tertiary system-2xs-medium-uppercase">{t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })}</div>
<div className="text-text-secondary system-xs-regular">{annotationReply.score_threshold || '-'}</div>
</div>
<div className="h-[27px] w-px rotate-12 bg-divider-subtle"></div>
<div className="">
<div className="system-2xs-medium-uppercase mb-0.5 text-text-tertiary">{t('modelProvider.embeddingModel.key', { ns: 'common' })}</div>
<div className="system-xs-regular text-text-secondary">{annotationReply.embedding_model?.embedding_model_name}</div>
<div className="mb-0.5 text-text-tertiary system-2xs-medium-uppercase">{t('modelProvider.embeddingModel.key', { ns: 'common' })}</div>
<div className="text-text-secondary system-xs-regular">{annotationReply.embedding_model?.embedding_model_name}</div>
</div>
</div>
)}

View File

@@ -38,7 +38,7 @@ const FeatureCard = ({
>
<div className="mb-2 flex items-center gap-2">
{icon}
<div className="system-sm-semibold flex grow items-center text-text-secondary">
<div className="flex grow items-center text-text-secondary system-sm-semibold">
{title}
{tooltip && (
<Tooltip
@@ -51,7 +51,7 @@ const FeatureCard = ({
<Switch disabled={disabled} className="shrink-0" onChange={state => onChange?.(state)} value={value} />
</div>
{description && (
<div className="system-xs-regular line-clamp-2 min-h-8 text-text-tertiary">{description}</div>
<div className="line-clamp-2 min-h-8 text-text-tertiary system-xs-regular">{description}</div>
)}
{children}
</div>

View File

@@ -111,7 +111,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
const arr = value.split('\n').reduce((prev: string[], next: string) => {
if (next !== '')
prev.push(next.slice(0, 100))
if (next === '' && prev[prev.length - 1] !== '')
if (next === '' && prev.at(-1) !== '')
prev.push(next)
return prev

View File

@@ -64,7 +64,7 @@ describe('FileImageItem', () => {
)
const svgs = container.querySelectorAll('svg')
const progressSvg = Array.from(svgs).find(svg => svg.querySelector('circle'))
const progressSvg = [...svgs].find(svg => svg.querySelector('circle'))
expect(progressSvg).toBeInTheDocument()
})
@@ -138,7 +138,7 @@ describe('FileImageItem', () => {
// Find the RiDownloadLine SVG (it doesn't have data-icon attribute, unlike ReplayLine)
const svgs = container.querySelectorAll('svg')
const downloadSvg = Array.from(svgs).find(
const downloadSvg = [...svgs].find(
svg => !svg.hasAttribute('data-icon') && !svg.querySelector('circle'),
)
fireEvent.click(downloadSvg!.parentElement!)
@@ -174,7 +174,7 @@ describe('FileImageItem', () => {
// The download SVG should be rendered
const svgs = container.querySelectorAll('svg')
expect(svgs.length).toBeGreaterThanOrEqual(1)
const downloadSvg = Array.from(svgs).find(
const downloadSvg = [...svgs].find(
svg => !svg.hasAttribute('data-icon') && !svg.querySelector('circle'),
)
fireEvent.click(downloadSvg!.parentElement!)
@@ -189,7 +189,7 @@ describe('FileImageItem', () => {
const { container } = render(<FileImageItem file={file} showDownloadAction />)
const svgs = container.querySelectorAll('svg')
const downloadSvg = Array.from(svgs).find(
const downloadSvg = [...svgs].find(
svg => !svg.hasAttribute('data-icon') && !svg.querySelector('circle'),
)
fireEvent.click(downloadSvg!.parentElement!)
@@ -233,7 +233,7 @@ describe('FileImageItem', () => {
const { container } = render(<FileImageItem file={file} showDownloadAction />)
const svgs = container.querySelectorAll('svg')
const downloadSvg = Array.from(svgs).find(
const downloadSvg = [...svgs].find(
svg => !svg.hasAttribute('data-icon') && !svg.querySelector('circle'),
)
fireEvent.click(downloadSvg!.parentElement!)

View File

@@ -51,7 +51,7 @@ describe('ImagePreview', () => {
// In some test environments `write` lives on the prototype rather than
// the clipboard instance itself; locate the actual owner so vi.spyOn
// patches the right object.
const writeOwner = Object.prototype.hasOwnProperty.call(clipboardTarget, 'write')
const writeOwner = Object.hasOwn(clipboardTarget, 'write')
? clipboardTarget
: (Object.getPrototypeOf(clipboardTarget) as { write: (items: ClipboardItem[]) => Promise<void> })
vi.spyOn(writeOwner, 'write').mockImplementation((items: ClipboardItem[]) => {

View File

@@ -26,7 +26,7 @@ type CapturedProps = {
const getLastWrapperProps = (): CapturedProps => {
const calls = mockReactMarkdownWrapper.mock.calls
const lastCall = calls[calls.length - 1]
const lastCall = calls.at(-1)
return lastCall[0] as CapturedProps
}

View File

@@ -32,7 +32,7 @@ describe('AudioBtn', () => {
}
const getAudioCallback = () => {
const lastCall = mockGetAudioPlayer.mock.calls[mockGetAudioPlayer.mock.calls.length - 1]
const lastCall = mockGetAudioPlayer.mock.calls.at(-1)
const callback = lastCall?.find((arg: unknown) => typeof arg === 'function') as ((event: string) => void) | undefined
if (!callback)
throw new Error('Audio callback not found - ensure mockGetAudioPlayer was called with a callback argument')

View File

@@ -92,7 +92,7 @@ const NotionPageSelector = ({
}, [notionsPages?.notion_info])
const defaultSelectedPagesId = useMemo(() => {
return [...Array.from(pagesMapAndSelectedPagesId[1]), ...(value || [])]
return [...[...pagesMapAndSelectedPagesId[1]], ...(value || [])]
}, [pagesMapAndSelectedPagesId, value])
const [selectedPagesId, setSelectedPagesId] = useState<Set<string>>(() => new Set(defaultSelectedPagesId))
@@ -113,9 +113,9 @@ const NotionPageSelector = ({
}, [datasetId, invalidPreImportNotionPages, notionCredentials, onSelect, onSelectCredential])
const handleSelectPages = useCallback((newSelectedPagesId: Set<string>) => {
const selectedPages = Array.from(newSelectedPagesId).map(pageId => pagesMapAndSelectedPagesId[0][pageId])
const selectedPages = Array.from(newSelectedPagesId, pageId => pagesMapAndSelectedPagesId[0][pageId])
setSelectedPagesId(new Set(Array.from(newSelectedPagesId)))
setSelectedPagesId(new Set([...newSelectedPagesId]))
onSelect(selectedPages)
}, [pagesMapAndSelectedPagesId, onSelect])

View File

@@ -236,8 +236,8 @@ const PageSelector = ({
const current = dataList[index]
const pageId = current.page_id
const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId]
const descendantsIds = Array.from(currentWithChildrenAndDescendants.descendants)
const childrenIds = Array.from(currentWithChildrenAndDescendants.children)
const descendantsIds = [...currentWithChildrenAndDescendants.descendants]
const childrenIds = [...currentWithChildrenAndDescendants.children]
let newDataList = []
if (current.expand) {

View File

@@ -76,7 +76,7 @@ describe('CustomizedPagination', () => {
render(<CustomizedPagination {...defaultProps} current={9} total={100} limit={10} />)
const buttons = screen.getAllByRole('button')
// Last button is next
expect(buttons[buttons.length - 1]).toBeDisabled()
expect(buttons.at(-1)).toBeDisabled()
})
it('should not render limit selector when onLimitChange is not provided', () => {
@@ -98,7 +98,7 @@ describe('CustomizedPagination', () => {
const onChange = vi.fn()
render(<CustomizedPagination {...defaultProps} current={0} onChange={onChange} />)
const buttons = screen.getAllByRole('button')
const nextButton = buttons[buttons.length - 1]
const nextButton = buttons.at(-1)
fireEvent.click(nextButton)
expect(onChange).toHaveBeenCalledWith(1)
})
@@ -226,7 +226,7 @@ describe('CustomizedPagination', () => {
// totalPages = 1, both buttons should be disabled
const buttons = screen.getAllByRole('button')
expect(buttons[0]).toBeDisabled()
expect(buttons[buttons.length - 1]).toBeDisabled()
expect(buttons.at(-1)).toBeDisabled()
})
it('should restore input value when blurred with empty value', () => {

View File

@@ -36,7 +36,7 @@ const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1,
}}
/>
)}
<span className="system-sm-semibold mr-1 text-text-secondary">{name}</span>
<span className="mr-1 text-text-secondary system-sm-semibold">{name}</span>
{!noTooltip && (
<Tooltip
triggerClassName="w-4 h-4 shrink-0"

View File

@@ -553,7 +553,7 @@ describe('useVariableOptions', () => {
() => useVariableOptions(makeVariableBlock([])),
{ wrapper },
)
const lastOption = result.current[result.current.length - 1]
const lastOption = result.current.at(-1)
const el = lastOption.renderMenuOption(renderProps)
expect(el).toBeTruthy()
})
@@ -569,7 +569,7 @@ describe('useVariableOptions', () => {
{ wrapper },
)
const spy = vi.spyOn(capturedEditor!, 'update')
const lastOption = result.current[result.current.length - 1]
const lastOption = result.current.at(-1)
lastOption.onSelectMenuOption()
expect(spy).toHaveBeenCalledTimes(1)
})
@@ -717,7 +717,7 @@ describe('useExternalToolOptions', () => {
() => useExternalToolOptions(makeExternalToolBlock({}, [])),
{ wrapper },
)
const lastOption = result.current[result.current.length - 1]
const lastOption = result.current.at(-1)
const el = lastOption.renderMenuOption(renderProps)
expect(el).toBeTruthy()
})
@@ -728,7 +728,7 @@ describe('useExternalToolOptions', () => {
() => useExternalToolOptions(makeExternalToolBlock({ onAddExternalTool }, [])),
{ wrapper },
)
const lastOption = result.current[result.current.length - 1]
const lastOption = result.current.at(-1)
lastOption.onSelectMenuOption()
expect(onAddExternalTool).toHaveBeenCalledTimes(1)
})
@@ -741,7 +741,7 @@ describe('useExternalToolOptions', () => {
() => useExternalToolOptions(block),
{ wrapper },
)
const lastOption = result.current[result.current.length - 1]
const lastOption = result.current.at(-1)
expect(() => lastOption.onSelectMenuOption()).not.toThrow()
})
})

View File

@@ -346,7 +346,7 @@ describe('ComponentPicker (component-picker-block/index.tsx)', () => {
// There is no accessible "option" role here (menu items are plain divs).
// We locate menu items by `tabindex="-1"` inside the listbox.
const listbox = await screen.findByRole('listbox', { name: /typeahead menu/i })
const menuItems = Array.from(listbox.querySelectorAll('[tabindex="-1"]'))
const menuItems = [...listbox.querySelectorAll('[tabindex="-1"]')]
// Expect at least: (1) our empty variable option, (2) the "add variable" option.
expect(menuItems.length).toBeGreaterThanOrEqual(2)

View File

@@ -50,7 +50,7 @@ const TagInput: FC<TagInputProps> = ({
return
}
if ((items.find(item => item === valueTrimmed))) {
if ((items.includes(valueTrimmed))) {
notify({ type: 'error', message: t('segment.keywordDuplicate', { ns: 'datasetDocuments' }) })
return
}

View File

@@ -16,7 +16,7 @@ vi.mock('@/service/base', () => ({
}))
const getLatestStreamOptions = (): IOtherOptions => {
const latestCall = mockSsePost.mock.calls[mockSsePost.mock.calls.length - 1]
const latestCall = mockSsePost.mock.calls.at(-1)
if (!latestCall)
throw new Error('Expected ssePost to be called at least once')
return latestCall[2]

View File

@@ -94,7 +94,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ src, srcs }) => {
if (controlsTimeoutRef.current)
clearTimeout(controlsTimeoutRef.current)
controlsTimeoutRef.current = setTimeout(() => setIsControlsVisible(false), 3000)
controlsTimeoutRef.current = setTimeout(setIsControlsVisible, 3000, false)
}, [])
const togglePlayPause = useCallback(() => {

View File

@@ -241,7 +241,7 @@ describe('PlanComp', () => {
await waitFor(() => expect(screen.getByTestId('verify-modal').getAttribute('data-is-show')).toBe('true'))
// Get the props passed to the modal and call onConfirm/onCancel
const lastCall = verifyStateModalMock.mock.calls[verifyStateModalMock.mock.calls.length - 1][0]
const lastCall = verifyStateModalMock.mock.calls.at(-1)[0]
expect(lastCall.onConfirm).toBeDefined()
expect(lastCall.onCancel).toBeDefined()

View File

@@ -155,7 +155,7 @@ describe('Enterprise Icon Component', () => {
it('should use CSS custom properties for colors', () => {
const { container } = render(<Enterprise />)
const allFillElements = container.querySelectorAll('[fill]')
const elementsWithCSSVars = Array.from(allFillElements).filter(el =>
const elementsWithCSSVars = [...allFillElements].filter(el =>
el.getAttribute('fill')?.startsWith('var('),
)

View File

@@ -120,7 +120,7 @@ describe('Professional Icon Component', () => {
it('should use CSS custom properties for colors', () => {
const { container } = render(<Professional />)
const allFillElements = container.querySelectorAll('[fill]')
const elementsWithCSSVars = Array.from(allFillElements).filter(el =>
const elementsWithCSSVars = [...allFillElements].filter(el =>
el.getAttribute('fill')?.startsWith('var('),
)

View File

@@ -111,7 +111,7 @@ describe('Sandbox Icon Component', () => {
it('should use CSS custom properties for colors', () => {
const { container } = render(<Sandbox />)
const allFillElements = container.querySelectorAll('[fill]')
const elementsWithCSSVars = Array.from(allFillElements).filter(el =>
const elementsWithCSSVars = [...allFillElements].filter(el =>
el.getAttribute('fill')?.startsWith('var('),
)

View File

@@ -134,7 +134,7 @@ describe('Team Icon Component', () => {
it('should use CSS custom properties for colors', () => {
const { container } = render(<Team />)
const allFillElements = container.querySelectorAll('[fill]')
const elementsWithCSSVars = Array.from(allFillElements).filter(el =>
const elementsWithCSSVars = [...allFillElements].filter(el =>
el.getAttribute('fill')?.startsWith('var('),
)

View File

@@ -44,28 +44,28 @@ describe('Pricing Assets', () => {
it('should render active state for Cloud', () => {
const { container } = render(<Cloud isActive />)
const rects = Array.from(container.querySelectorAll('rect'))
const rects = [...container.querySelectorAll('rect')]
expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-saas-dify-blue-accessible)')).toBe(true)
})
it('should render inactive state for Cloud', () => {
const { container } = render(<Cloud isActive={false} />)
const rects = Array.from(container.querySelectorAll('rect'))
const rects = [...container.querySelectorAll('rect')]
expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-text-primary)')).toBe(true)
})
it('should render active state for SelfHosted', () => {
const { container } = render(<SelfHosted isActive />)
const rects = Array.from(container.querySelectorAll('rect'))
const rects = [...container.querySelectorAll('rect')]
expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-saas-dify-blue-accessible)')).toBe(true)
})
it('should render inactive state for SelfHosted', () => {
const { container } = render(<SelfHosted isActive={false} />)
const rects = Array.from(container.querySelectorAll('rect'))
const rects = [...container.querySelectorAll('rect')]
expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-text-primary)')).toBe(true)
})
})

View File

@@ -156,7 +156,7 @@ describe('ImagePreviewer', () => {
// Find and click next button (right arrow)
const buttons = document.querySelectorAll('button')
const nextButton = Array.from(buttons).find(btn =>
const nextButton = [...buttons].find(btn =>
btn.className.includes('right-8'),
)
@@ -185,7 +185,7 @@ describe('ImagePreviewer', () => {
// Find and click prev button (left arrow)
const buttons = document.querySelectorAll('button')
const prevButton = Array.from(buttons).find(btn =>
const prevButton = [...buttons].find(btn =>
btn.className.includes('left-8'),
)
@@ -209,7 +209,7 @@ describe('ImagePreviewer', () => {
})
const buttons = document.querySelectorAll('button')
const prevButton = Array.from(buttons).find(btn =>
const prevButton = [...buttons].find(btn =>
btn.className.includes('left-8'),
)
@@ -225,7 +225,7 @@ describe('ImagePreviewer', () => {
})
const buttons = document.querySelectorAll('button')
const nextButton = Array.from(buttons).find(btn =>
const nextButton = [...buttons].find(btn =>
btn.className.includes('right-8'),
)
@@ -294,7 +294,7 @@ describe('ImagePreviewer', () => {
})
const buttons = document.querySelectorAll('button')
const prevButton = Array.from(buttons).find(btn =>
const prevButton = [...buttons].find(btn =>
btn.className.includes('left-8'),
)
@@ -324,7 +324,7 @@ describe('ImagePreviewer', () => {
})
const buttons = document.querySelectorAll('button')
const nextButton = Array.from(buttons).find(btn =>
const nextButton = [...buttons].find(btn =>
btn.className.includes('right-8'),
)
@@ -398,7 +398,7 @@ describe('ImagePreviewer', () => {
// Find and click the retry button (not the nav buttons)
const allButtons = document.querySelectorAll('button')
const retryButton = Array.from(allButtons).find(btn =>
const retryButton = [...allButtons].find(btn =>
btn.className.includes('rounded-full') && !btn.className.includes('left-8') && !btn.className.includes('right-8'),
)
@@ -459,10 +459,10 @@ describe('ImagePreviewer', () => {
// Both navigation buttons should be disabled
const buttons = document.querySelectorAll('button')
const prevButton = Array.from(buttons).find(btn =>
const prevButton = [...buttons].find(btn =>
btn.className.includes('left-8'),
)
const nextButton = Array.from(buttons).find(btn =>
const nextButton = [...buttons].find(btn =>
btn.className.includes('right-8'),
)

View File

@@ -127,7 +127,7 @@ const RetrievalParamConfig: FC<Props> = ({
/>
)}
<div className="flex items-center">
<span className="system-sm-semibold mr-0.5 text-text-secondary">{t('modelProvider.rerankModel.key', { ns: 'common' })}</span>
<span className="mr-0.5 text-text-secondary system-sm-semibold">{t('modelProvider.rerankModel.key', { ns: 'common' })}</span>
<Tooltip
popupContent={
<div className="w-[200px]">{t('modelProvider.rerankModel.tip', { ns: 'common' })}</div>
@@ -157,7 +157,7 @@ const RetrievalParamConfig: FC<Props> = ({
<div className="p-1">
<AlertTriangle className="size-4 text-text-warning-secondary" />
</div>
<span className="system-xs-medium text-text-primary">
<span className="text-text-primary system-xs-medium">
{t('form.retrievalSetting.multiModalTip', { ns: 'datasetSettings' })}
</span>
</div>
@@ -281,7 +281,7 @@ const RetrievalParamConfig: FC<Props> = ({
<div className="p-1">
<AlertTriangle className="size-4 text-text-warning-secondary" />
</div>
<span className="system-xs-medium text-text-primary">
<span className="text-text-primary system-xs-medium">
{t('form.retrievalSetting.multiModalTip', { ns: 'datasetSettings' })}
</span>
</div>

View File

@@ -82,7 +82,7 @@ describe('CreateFromPipeline', () => {
describe('Component Order', () => {
it('should render components in correct order', () => {
const { container } = render(<CreateFromPipeline />)
const children = Array.from(container.firstChild?.childNodes || [])
const children = [...container.firstChild?.childNodes || []]
// Effect, Header, List, Footer
expect(children.length).toBe(4)

View File

@@ -1131,7 +1131,7 @@ describe('CreateFromDSLModal', () => {
// There are two Cancel buttons now (one in main modal footer, one in error modal)
// Find the Cancel button in the error modal context
const cancelButtons = screen.getAllByText('app.newApp.Cancel')
fireEvent.click(cancelButtons[cancelButtons.length - 1])
fireEvent.click(cancelButtons.at(-1))
vi.useRealTimers()
})

View File

@@ -54,7 +54,7 @@ const Uploader: FC<Props> = ({
setDragging(false)
if (!e.dataTransfer)
return
const files = Array.from(e.dataTransfer.files)
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('stepOne.uploader.validation.count', { ns: 'datasetCreation' }) })
return

View File

@@ -49,7 +49,7 @@ describe('List', () => {
describe('Component Order', () => {
it('should render BuiltInPipelineList before CustomizedList', () => {
const { container } = render(<List />)
const children = Array.from(container.firstChild?.childNodes || [])
const children = [...container.firstChild?.childNodes || []]
expect(children.length).toBe(2)
expect((children[0] as HTMLElement).getAttribute('data-testid')).toBe('built-in-list')

View File

@@ -112,7 +112,7 @@ describe('FilePreview', () => {
it('should show loading indicator initially', async () => {
// Arrange - Delay API response to keep loading state
mockFetchFilePreview.mockImplementation(
() => new Promise(resolve => setTimeout(() => resolve({ content: 'test' }), 100)),
() => new Promise(resolve => setTimeout(resolve, 100, { content: 'test' })),
)
const { container } = renderFilePreview()
@@ -607,7 +607,7 @@ describe('FilePreview', () => {
it('should handle unmount during loading', async () => {
mockFetchFilePreview.mockImplementation(
() => new Promise(resolve => setTimeout(() => resolve({ content: 'delayed' }), 1000)),
() => new Promise(resolve => setTimeout(resolve, 1000, { content: 'delayed' })),
)
const { unmount } = renderFilePreview()

View File

@@ -189,7 +189,7 @@ describe('FileUploader', () => {
// Find the delete button (the span with cursor-pointer containing the icon)
const deleteButtons = container.querySelectorAll('[class*="cursor-pointer"]')
// Get the last one which should be the delete button (not the browse label)
const deleteButton = deleteButtons[deleteButtons.length - 1]
const deleteButton = deleteButtons.at(-1)
if (deleteButton)
fireEvent.click(deleteButton)

View File

@@ -98,8 +98,7 @@ export const useFileUpload = ({
docx: 'docx',
}
return [...supportTypes]
.map(item => extensionMap[item] || item)
return Array.from(supportTypes, item => extensionMap[item] || item)
.map(item => item.toLowerCase())
.filter((item, index, self) => self.indexOf(item) === index)
.map(item => item.toUpperCase())
@@ -271,7 +270,7 @@ export const useFileUpload = ({
if (!e.dataTransfer)
return
const nested = await Promise.all(
Array.from(e.dataTransfer.items).map((it) => {
Array.from(e.dataTransfer.items, (it) => {
const entry = (it as DataTransferItem & { webkitGetAsEntry?: () => FileSystemEntry | null }).webkitGetAsEntry?.()
if (entry)
return traverseFileEntry(entry)
@@ -303,7 +302,7 @@ export const useFileUpload = ({
}, [onFileListUpdate])
const fileChangeHandle = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
let files = Array.from(e.target.files ?? []) as File[]
let files = [...e.target.files ?? []] as File[]
files = files.slice(0, fileUploadConfig.batch_count_limit)
initialUpload(files.filter(isValid))
}, [isValid, initialUpload, fileUploadConfig])

View File

@@ -188,7 +188,7 @@ describe('NotionPagePreview', () => {
it('should show loading indicator initially', async () => {
// Arrange - Delay API response to keep loading state
mockFetchNotionPagePreview.mockImplementation(
() => new Promise(resolve => setTimeout(() => resolve({ content: 'test' }), 100)),
() => new Promise(resolve => setTimeout(resolve, 100, { content: 'test' })),
)
// Act - Don't wait for content to load
@@ -701,7 +701,7 @@ describe('NotionPagePreview', () => {
it('should handle unmount during loading', async () => {
mockFetchNotionPagePreview.mockImplementation(
() => new Promise(resolve => setTimeout(() => resolve({ content: 'delayed' }), 1000)),
() => new Promise(resolve => setTimeout(resolve, 1000, { content: 'delayed' })),
)
// Act - Don't wait for content

View File

@@ -25,7 +25,7 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = (props) =>
return (
<FormField label={(
<div className="mb-1 flex items-center">
<span className="system-sm-semibold mr-0.5">{t('stepTwo.separator', { ns: 'datasetCreation' })}</span>
<span className="mr-0.5 system-sm-semibold">{t('stepTwo.separator', { ns: 'datasetCreation' })}</span>
<Tooltip
popupContent={(
<div className="max-w-[200px]">
@@ -52,7 +52,7 @@ export const MaxLengthInput: FC<InputNumberProps> = (props) => {
const { t } = useTranslation()
return (
<FormField label={(
<div className="system-sm-semibold mb-1">
<div className="mb-1 system-sm-semibold">
{t('stepTwo.maxLength', { ns: 'datasetCreation' })}
</div>
)}

View File

@@ -251,7 +251,7 @@ describe('PreviewItem', () => {
// Assert - Check content is in pre-line div
const preLineDivs = container.querySelectorAll('[style*="white-space: pre-line"]')
const questionDiv = Array.from(preLineDivs).find(div => div.textContent?.includes('Question line 1'))
const questionDiv = [...preLineDivs].find(div => div.textContent?.includes('Question line 1'))
expect(questionDiv).toBeTruthy()
expect(questionDiv?.textContent).toContain('Question line 2')
})
@@ -268,7 +268,7 @@ describe('PreviewItem', () => {
// Assert - Check content is in pre-line div
const preLineDivs = container.querySelectorAll('[style*="white-space: pre-line"]')
const answerDiv = Array.from(preLineDivs).find(div => div.textContent?.includes('Answer line 1'))
const answerDiv = [...preLineDivs].find(div => div.textContent?.includes('Answer line 1'))
expect(answerDiv).toBeTruthy()
expect(answerDiv?.textContent).toContain('Answer line 2')
})

View File

@@ -295,7 +295,7 @@ describe('StopEmbeddingModal', () => {
// Act - Find the close span (it should be the span with onClick handler)
const spans = container.querySelectorAll('span')
const closeSpan = Array.from(spans).find(span =>
const closeSpan = [...spans].find(span =>
span.className && span.getAttribute('class')?.includes('close'),
)
@@ -318,7 +318,7 @@ describe('StopEmbeddingModal', () => {
const { container } = renderStopEmbeddingModal({ onConfirm, onHide })
const spans = container.querySelectorAll('span')
const closeSpan = Array.from(spans).find(span =>
const closeSpan = [...spans].find(span =>
span.className && span.getAttribute('class')?.includes('close'),
)

View File

@@ -27,7 +27,7 @@ vi.mock('@/app/components/base/file-uploader/utils', () => ({
vi.mock('@/utils/format', () => ({
getFileExtension: (filename: string) => {
const parts = filename.split('.')
return parts[parts.length - 1] || ''
return parts.at(-1) || ''
},
}))
@@ -896,7 +896,7 @@ describe('useLocalFileUpload', () => {
await waitFor(() => {
const calls = mockSetLocalFileList.mock.calls
const lastCall = calls[calls.length - 1][0]
const lastCall = calls.at(-1)[0]
expect(lastCall.some((f: FileItem) => f.progress === PROGRESS_ERROR)).toBe(true)
})
})

View File

@@ -61,7 +61,7 @@ describe('FilePreview', () => {
it('should call hidePreview when close button clicked', () => {
render(<FilePreview {...defaultProps} />)
const buttons = screen.getAllByRole('button')
const closeBtn = buttons[buttons.length - 1]
const closeBtn = buttons.at(-1)
fireEvent.click(closeBtn)
expect(defaultProps.hidePreview).toHaveBeenCalled()
})

View File

@@ -41,7 +41,7 @@ describe('WebPreview', () => {
it('should call hidePreview when close button clicked', () => {
render(<WebPreview {...defaultProps} />)
const buttons = screen.getAllByRole('button')
const closeBtn = buttons[buttons.length - 1]
const closeBtn = buttons.at(-1)
fireEvent.click(closeBtn)
expect(defaultProps.hidePreview).toHaveBeenCalled()
})

View File

@@ -62,10 +62,10 @@ const CSVUploader: FC<Props> = ({
onprogress: onProgress,
}, false, undefined, '?source=datasets')
.then((res: UploadResult) => {
const updatedFile = Object.assign({}, fileItem.file, {
const updatedFile = { ...fileItem.file, ...{
id: res.id,
...(res as Partial<File>),
}) as File
} } as File
const completeFile: FileItem = {
fileID: fileItem.fileID,
file: updatedFile,
@@ -126,7 +126,7 @@ const CSVUploader: FC<Props> = ({
setDragging(false)
if (!e.dataTransfer)
return
const files = Array.from(e.dataTransfer.files)
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('stepOne.uploader.validation.count', { ns: 'datasetCreation' }) })
return
@@ -148,7 +148,7 @@ const CSVUploader: FC<Props> = ({
return ''
const arr = currentFile.name.split('.')
return arr[arr.length - 1]
return arr.at(-1)
}
const isValid = useCallback((file?: File) => {
@@ -204,7 +204,7 @@ const CSVUploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}>
<div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}>
<div className="flex w-full items-center justify-center space-x-2">
<CSVIcon className="shrink-0" />
<div className="text-text-secondary">

View File

@@ -149,7 +149,7 @@ describe('ActionButtons', () => {
)
const buttons = screen.getAllByRole('button')
const saveButton = buttons[buttons.length - 1] // Save button is last
const saveButton = buttons.at(-1) // Save button is last
fireEvent.click(saveButton)
expect(mockHandleSave).toHaveBeenCalledTimes(1)
@@ -166,7 +166,7 @@ describe('ActionButtons', () => {
)
const buttons = screen.getAllByRole('button')
const saveButton = buttons[buttons.length - 1]
const saveButton = buttons.at(-1)
expect(saveButton).toBeDisabled()
})
})

View File

@@ -58,7 +58,7 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({
<Divider type="vertical" className="mx-1 h-3 bg-divider-regular" />
<button
type="button"
className="system-xs-semibold text-text-accent"
className="text-text-accent system-xs-semibold"
onClick={() => {
clearTimeout(refreshTimer.current)
viewNewlyAddedChildChunk?.()
@@ -120,11 +120,11 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({
<div className="flex h-full flex-col">
<div className={cn('flex items-center justify-between', fullScreen ? 'border border-divider-subtle py-3 pl-6 pr-4' : 'pl-4 pr-3 pt-3')}>
<div className="flex flex-col">
<div className="system-xl-semibold text-text-primary">{t('segment.addChildChunk', { ns: 'datasetDocuments' })}</div>
<div className="text-text-primary system-xl-semibold">{t('segment.addChildChunk', { ns: 'datasetDocuments' })}</div>
<div className="flex items-center gap-x-2">
<SegmentIndexTag label={t('segment.newChildChunk', { ns: 'datasetDocuments' }) as string} />
<Dot />
<span className="system-xs-medium text-text-tertiary">{wordCountText}</span>
<span className="text-text-tertiary system-xs-medium">{wordCountText}</span>
</div>
</div>
<div className="flex items-center">

View File

@@ -155,9 +155,9 @@ const SegmentCard: FC<ISegmentCardProps> = ({
labelPrefix={labelPrefix}
/>
<Dot />
<div className={cn('system-xs-medium text-text-tertiary', contentOpacity)}>{wordCountText}</div>
<div className={cn('text-text-tertiary system-xs-medium', contentOpacity)}>{wordCountText}</div>
<Dot />
<div className={cn('system-xs-medium text-text-tertiary', contentOpacity)}>{`${formatNumber(hit_count)} ${t('segment.hitCount', { ns: 'datasetDocuments' })}`}</div>
<div className={cn('text-text-tertiary system-xs-medium', contentOpacity)}>{`${formatNumber(hit_count)} ${t('segment.hitCount', { ns: 'datasetDocuments' })}`}</div>
{chunkEdited && (
<>
<Dot />
@@ -254,7 +254,7 @@ const SegmentCard: FC<ISegmentCardProps> = ({
? (
<button
type="button"
className="system-xs-semibold-uppercase mb-2 mt-0.5 text-text-accent"
className="mb-2 mt-0.5 text-text-accent system-xs-semibold-uppercase"
onClick={() => onClick?.()}
>
{t('operation.viewMore', { ns: 'common' })}

View File

@@ -57,7 +57,7 @@ const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
onSuccess: (res) => {
setImportStatus(res.job_status)
if (res.job_status === ProcessStatus.WAITING || res.job_status === ProcessStatus.PROCESSING)
setTimeout(() => checkProcess(res.job_id), 2500)
setTimeout(checkProcess, 2500, res.job_id)
if (res.job_status === ProcessStatus.ERROR)
Toast.notify({ type: 'error', message: `${t('list.batchModal.runError', { ns: 'datasetDocuments' })}` })
},

View File

@@ -61,7 +61,7 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
<Divider type="vertical" className="mx-1 h-3 bg-divider-regular" />
<button
type="button"
className="system-xs-semibold text-text-accent"
className="text-text-accent system-xs-semibold"
onClick={() => {
clearTimeout(refreshTimer.current)
viewNewlyAddedChunk()
@@ -158,13 +158,13 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
className={cn('flex items-center justify-between', fullScreen ? 'border border-divider-subtle py-3 pl-6 pr-4' : 'pl-4 pr-3 pt-3')}
>
<div className="flex flex-col">
<div className="system-xl-semibold text-text-primary">
<div className="text-text-primary system-xl-semibold">
{t('segment.addChunk', { ns: 'datasetDocuments' })}
</div>
<div className="flex items-center gap-x-2">
<SegmentIndexTag label={t('segment.newChunk', { ns: 'datasetDocuments' })!} />
<Dot />
<span className="system-xs-medium text-text-tertiary">{wordCountText}</span>
<span className="text-text-tertiary system-xs-medium">{wordCountText}</span>
</div>
</div>
<div className="flex items-center">

View File

@@ -164,7 +164,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('page')).toBe('2')
expect(update.options.history).toBe('push')
})
@@ -177,7 +177,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('status')).toBe('error')
})
@@ -189,7 +189,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('status')).toBe(false)
})
@@ -201,7 +201,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('sort')).toBe('hit_count')
})
@@ -213,7 +213,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('sort')).toBe(false)
})
@@ -225,7 +225,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('keyword')).toBe('test query')
expect(update.options.history).toBe('replace')
})
@@ -238,7 +238,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('keyword')).toBe('hello')
expect(update.searchParams.has('page')).toBe(false)
expect(update.options.history).toBe('replace')
@@ -252,7 +252,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('keyword')).toBe(false)
expect(update.options.history).toBe('replace')
})
@@ -265,7 +265,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('keyword')).toBe(false)
expect(result.current.query.keyword).toBe('')
})
@@ -278,7 +278,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('keyword')).toBe('%2F')
expect(result.current.query.keyword).toBe('%2F')
})
@@ -302,7 +302,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('status')).toBe(false)
})
@@ -314,7 +314,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('sort')).toBe(false)
})
@@ -326,7 +326,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('page')).toBe(false)
})
@@ -338,7 +338,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('page')).toBe('2')
})
@@ -350,7 +350,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.get('limit')).toBe('25')
})
@@ -362,7 +362,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('page')).toBe(false)
expect(result.current.query.page).toBe(1)
})
@@ -375,7 +375,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('limit')).toBe(false)
expect(result.current.query.limit).toBe(10)
})
@@ -406,7 +406,7 @@ describe('useDocumentListQueryState', () => {
})
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0]
const update = onUrlUpdate.mock.calls.at(-1)[0]
expect(update.searchParams.has('page')).toBe(false)
expect(update.searchParams.has('status')).toBe(false)
})

View File

@@ -362,7 +362,7 @@ describe('AddExternalAPIModal', () => {
// There are multiple cancel buttons, find the one in the confirm dialog
const cancelButtons = screen.getAllByRole('button', { name: /cancel/i })
const confirmDialogCancelButton = cancelButtons[cancelButtons.length - 1]
const confirmDialogCancelButton = cancelButtons.at(-1)
fireEvent.click(confirmDialogCancelButton)
await waitFor(() => {

View File

@@ -125,13 +125,13 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
<div className="fixed inset-0 flex items-center justify-center bg-black/[.25]">
<div className="shadows-shadow-xl relative flex w-[480px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg">
<div className="flex flex-col items-start gap-2 self-stretch pb-3 pl-6 pr-14 pt-6">
<div className="title-2xl-semi-bold grow self-stretch text-text-primary">
<div className="grow self-stretch text-text-primary title-2xl-semi-bold">
{
isEditMode ? t('editExternalAPIFormTitle', { ns: 'dataset' }) : t('createExternalAPI', { ns: 'dataset' })
}
</div>
{isEditMode && (datasetBindings?.length ?? 0) > 0 && (
<div className="system-xs-regular flex items-center text-text-tertiary">
<div className="flex items-center text-text-tertiary system-xs-regular">
{t('editExternalAPIFormWarning.front', { ns: 'dataset' })}
<span className="flex cursor-pointer items-center text-text-accent">
&nbsp;
@@ -144,12 +144,12 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
popupContent={(
<div className="p-1">
<div className="flex items-start self-stretch pb-0.5 pl-2 pr-3 pt-1">
<div className="system-xs-medium-uppercase text-text-tertiary">{`${datasetBindings?.length} ${t('editExternalAPITooltipTitle', { ns: 'dataset' })}`}</div>
<div className="text-text-tertiary system-xs-medium-uppercase">{`${datasetBindings?.length} ${t('editExternalAPITooltipTitle', { ns: 'dataset' })}`}</div>
</div>
{datasetBindings?.map(binding => (
<div key={binding.id} className="flex items-center gap-1 self-stretch px-2 py-1">
<RiBook2Line className="h-4 w-4 text-text-secondary" />
<div className="system-sm-medium text-text-secondary">{binding.name}</div>
<div className="text-text-secondary system-sm-medium">{binding.name}</div>
</div>
))}
</div>
@@ -193,8 +193,8 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCan
{t('externalAPIForm.save', { ns: 'dataset' })}
</Button>
</div>
<div className="system-xs-regular flex items-center justify-center gap-1 self-stretch rounded-b-2xl border-t-[0.5px]
border-divider-subtle bg-background-soft px-2 py-3 text-text-tertiary"
<div className="flex items-center justify-center gap-1 self-stretch rounded-b-2xl border-t-[0.5px] border-divider-subtle
bg-background-soft px-2 py-3 text-text-tertiary system-xs-regular"
>
<RiLock2Fill className="h-3 w-3 text-text-quaternary" />
{t('externalAPIForm.encrypted.front', { ns: 'dataset' })}

View File

@@ -65,7 +65,7 @@ const Card = ({
disabled={!isCurrentWorkspaceManager}
/>
</div>
<div className="system-xs-regular text-text-tertiary">
<div className="text-text-tertiary system-xs-regular">
{t('appMenus.apiAccessTip', { ns: 'common' })}
</div>
</div>
@@ -79,7 +79,7 @@ const Card = ({
className="flex h-8 items-center space-x-[7px] rounded-lg px-2 text-text-tertiary hover:bg-state-base-hover"
>
<RiBookOpenLine className="size-3.5 shrink-0" />
<div className="system-sm-regular grow truncate">
<div className="grow truncate system-sm-regular">
{t('overview.apiInfo.doc', { ns: 'appOverview' })}
</div>
<RiArrowRightUpLine className="size-3.5 shrink-0" />

View File

@@ -555,7 +555,7 @@ describe('Card (service-api)', () => {
})
it('should handle very long apiBaseUrl', () => {
const longUrl = 'https://'.concat('a'.repeat(500), '.com')
const longUrl = [...'https://', ...'a'.repeat(500), ...'.com']
render(<Card apiBaseUrl={longUrl} />)
expect(screen.getByText(/serviceApi\.card\.title/i)).toBeInTheDocument()
})

View File

@@ -45,7 +45,7 @@ const { EditSlice } = await import('../edit-slice')
// Helper to find divider span (zero-width space)
const findDividerSpan = (container: HTMLElement) =>
Array.from(container.querySelectorAll('span')).find(s => s.textContent?.includes('\u200B'))
[...container.querySelectorAll('span')].find(s => s.textContent?.includes('\u200B'))
describe('EditSlice', () => {
const defaultProps = {

Some files were not shown because too many files have changed in this diff Show More