mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
Merge branch 'main' into tp
This commit is contained in:
commit
e5b5c1fa3b
2
.github/workflows/style.yml
vendored
2
.github/workflows/style.yml
vendored
@ -110,6 +110,8 @@ jobs:
|
||||
- name: Web tsslint
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
working-directory: ./web
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
run: vp run lint:tss
|
||||
|
||||
- name: Web type check
|
||||
|
||||
@ -6,7 +6,7 @@ requires-python = "~=3.12.0"
|
||||
dependencies = [
|
||||
# Legacy: mature and widely deployed
|
||||
"bleach>=6.3.0",
|
||||
"boto3>=1.42.91",
|
||||
"boto3>=1.42.96",
|
||||
"celery>=5.6.3",
|
||||
"croniter>=6.2.2",
|
||||
"flask>=3.1.3,<4.0.0",
|
||||
@ -185,12 +185,12 @@ dev = [
|
||||
storage = [
|
||||
"azure-storage-blob>=12.28.0",
|
||||
"bce-python-sdk>=0.9.70",
|
||||
"cos-python-sdk-v5>=1.9.41",
|
||||
"cos-python-sdk-v5>=1.9.42",
|
||||
"esdk-obs-python>=3.22.2",
|
||||
"google-cloud-storage>=3.10.1",
|
||||
"opendal>=0.46.0",
|
||||
"oss2>=2.19.1",
|
||||
"supabase>=2.28.3",
|
||||
"supabase>=2.29.0",
|
||||
"tos>=2.9.0",
|
||||
]
|
||||
|
||||
|
||||
60
api/uv.lock
generated
60
api/uv.lock
generated
@ -604,16 +604,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.42.91"
|
||||
version = "1.42.96"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/c0/98b8cec7ca22dde776df48c58940ae1abc425593959b7226e270760d726f/boto3-1.42.91.tar.gz", hash = "sha256:03d70532b17f7f84df37ca7e8c21553280454dea53ae12b15d1cfef9b16fcb8a", size = 113181, upload-time = "2026-04-17T19:31:06.251Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/2d/69fb3acd50bab83fb295c167d33c4b653faeb5fb0f42bfca4d9b69d6fb68/boto3-1.42.96.tar.gz", hash = "sha256:b38a9e4a3fbbee9017252576f1379780d0a5814768676c08df2f539d31fcdd68", size = 113203, upload-time = "2026-04-24T19:47:18.677Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/29/faba6521257c34085cc9b439ef98235b581772580f417fa3629728007270/boto3-1.42.91-py3-none-any.whl", hash = "sha256:04e72071cde022951ce7f81bd9933c90095ab8923e8ced61c8dacfe9edac0f5c", size = 140553, upload-time = "2026-04-17T19:31:02.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9d/b3f617d011c42eb804d993103b8fa9acdce153e181a3042f58bfe33d7cb4/boto3-1.42.96-py3-none-any.whl", hash = "sha256:2f4566da2c209a98bdbfc874d813ef231c84ad24e4f815e9bc91de5f63351a24", size = 140557, upload-time = "2026-04-24T19:47:15.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -636,16 +636,16 @@ bedrock-runtime = [
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.42.91"
|
||||
version = "1.42.96"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/bc/a4b7c46471c2e789ad8c4c7acfd7f302fdb481d93ff870f441249b924ae6/botocore-1.42.91.tar.gz", hash = "sha256:d252e27bc454afdbf5ed3dc617aa423f2c855c081e98b7963093399483ecc698", size = 15213010, upload-time = "2026-04-17T19:30:50.793Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/77/2c333622a1d47cf5bf73cdcab0cb6c92addafbef2ec05f81b9f75687d9e5/botocore-1.42.96.tar.gz", hash = "sha256:75b3b841ffacaa944f645196655a21ca777591dd8911e732bfb6614545af0250", size = 15263344, upload-time = "2026-04-24T19:47:05.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/fc/24cc0a47c824f13933e210e9ad034b4fba22f7185b8d904c0fbf5a3b2be8/botocore-1.42.91-py3-none-any.whl", hash = "sha256:7a28c3cc6bfab5724ad18899d52402b776a0de7d87fa20c3c5270bcaaf199ce8", size = 14897344, upload-time = "2026-04-17T19:30:44.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/56/152c3a859ca1b9d77ed16deac3cf81682013677c68cf5715698781fc81bd/botocore-1.42.96-py3-none-any.whl", hash = "sha256:db2c3e2006628be6fde81a24124a6563c363d6982fb92728837cf174bad9d98a", size = 14945920, upload-time = "2026-04-24T19:47:00.323Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1058,7 +1058,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cos-python-sdk-v5"
|
||||
version = "1.9.41"
|
||||
version = "1.9.42"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "crcmod" },
|
||||
@ -1067,9 +1067,9 @@ dependencies = [
|
||||
{ name = "six" },
|
||||
{ name = "xmltodict" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/38/c0029f413f51238aa2319715f45d74bcae931768e36c7e4604b02f407c6c/cos_python_sdk_v5-1.9.41.tar.gz", hash = "sha256:68f4be7d8fe27a1d186b3159b93c622816e398effdc236eddd442b86db592b82", size = 102625, upload-time = "2026-01-06T07:00:11.692Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/40/e3/b903b4acde334510f481d126a686bc4013710c00e2af34bff369511329ac/cos_python_sdk_v5-1.9.42.tar.gz", hash = "sha256:2a01d1868f50c5a70771f2b67da868f1dc6c6f3890f8009715313834404decc4", size = 102670, upload-time = "2026-04-23T11:08:27.949Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/2f/ead3fb551509fdc94e4a42093b770e3de2827ff7227570165df5e35c2a3e/cos_python_sdk_v5-1.9.41-py3-none-any.whl", hash = "sha256:f465aae43a4ba3f1caa8caeaca838d0395932f6848e89d6dde2807725e3c88a0", size = 98285, upload-time = "2026-01-06T06:43:02.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/bf/4ea660bb79d91fd41ba394605eccffd3d0943ed547b3fe2bdc6c7a52d2d1/cos_python_sdk_v5-1.9.42-py3-none-any.whl", hash = "sha256:02e583a1094e1794e6c0f56618d5190eb9eb7bfe75909f1dfac41bbee46e46c5", size = 98375, upload-time = "2026-04-23T11:05:14.519Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1578,7 +1578,7 @@ requires-dist = [
|
||||
{ name = "aliyun-log-python-sdk", specifier = ">=0.9.44,<1.0.0" },
|
||||
{ name = "azure-identity", specifier = ">=1.25.3,<2.0.0" },
|
||||
{ name = "bleach", specifier = ">=6.3.0" },
|
||||
{ name = "boto3", specifier = ">=1.42.91" },
|
||||
{ name = "boto3", specifier = ">=1.42.96" },
|
||||
{ name = "celery", specifier = ">=5.6.3" },
|
||||
{ name = "croniter", specifier = ">=6.2.2" },
|
||||
{ name = "fastopenapi", extras = ["flask"], specifier = "~=0.7.0" },
|
||||
@ -1684,12 +1684,12 @@ dev = [
|
||||
storage = [
|
||||
{ name = "azure-storage-blob", specifier = ">=12.28.0" },
|
||||
{ name = "bce-python-sdk", specifier = ">=0.9.70" },
|
||||
{ name = "cos-python-sdk-v5", specifier = ">=1.9.41" },
|
||||
{ name = "cos-python-sdk-v5", specifier = ">=1.9.42" },
|
||||
{ name = "esdk-obs-python", specifier = ">=3.22.2" },
|
||||
{ name = "google-cloud-storage", specifier = ">=3.10.1" },
|
||||
{ name = "opendal", specifier = ">=0.46.0" },
|
||||
{ name = "oss2", specifier = ">=2.19.1" },
|
||||
{ name = "supabase", specifier = ">=2.28.3" },
|
||||
{ name = "supabase", specifier = ">=2.29.0" },
|
||||
{ name = "tos", specifier = ">=2.9.0" },
|
||||
]
|
||||
tools = [
|
||||
@ -4810,7 +4810,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "postgrest"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@ -4818,9 +4818,9 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/40/60/9378ddd6e21b6005b34aeb42dc7a9ed9985c673c97c9b6a1858f9c52ebbd/postgrest-2.28.3.tar.gz", hash = "sha256:56336e9304950a78315ec7d6c8eb307cdb964d0878a7bec6111392ddb6c16a45", size = 13758, upload-time = "2026-03-20T14:38:06.542Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/98/f216b8b5c4d116ab6a2fb21339b5821da279ee773e163612418e1c56c012/postgrest-2.29.0.tar.gz", hash = "sha256:a87081858f627fcd57e8e7137004a1ef0adbdf0dbdfed1384e9ea1d7a9c525ec", size = 14217, upload-time = "2026-04-24T13:13:00.281Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/5e/6eeb1d53d010d80e800204c1eee6b3d5419a6a2b985c364f56f36cf48cca/postgrest-2.28.3-py3-none-any.whl", hash = "sha256:5a44d6c6d509abdbe0f928c86f0dc31ef26bda36e0357129836ec54dfb50b083", size = 21865, upload-time = "2026-03-20T14:38:05.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/0b/08b670a93a90d625c557b9e64b8a5fdeec80c3542d2d0265f0b4d6b16646/postgrest-2.29.0-py3-none-any.whl", hash = "sha256:3ee48e146f726272733d20e2b12de354cdb6cb9dd9cc3a61ed97ce69047aeb96", size = 22735, upload-time = "2026-04-24T13:12:58.405Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5723,16 +5723,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "realtime"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/3d/ef6ed9221f98766f3a503e6e3ac68fa7ca25c117b383f1efc448294232ac/realtime-2.28.3.tar.gz", hash = "sha256:5cc83a6217874426799d8bf74e96d904ac6fa77c39fa8982fa99287947eb2cbf", size = 18723, upload-time = "2026-03-20T14:38:08.424Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/f1/08c42a42653942fadfbef495d5b0239356140e7186cc528704956c5f06d4/realtime-2.29.0.tar.gz", hash = "sha256:8efe4a1b3a548a5fda09de701bd041fa0970c5a2fe7d13db0b9861ce11828be2", size = 18715, upload-time = "2026-04-24T13:13:02.315Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/d5/659405f9d4c9b022b7ac02bd52986ccc081f211db081051440f46bf4f358/realtime-2.28.3-py3-none-any.whl", hash = "sha256:efe484d6d39024c7e00ef70f70be600142e9407e5d802de8c96e86e014ce3b36", size = 22378, upload-time = "2026-03-20T14:38:07.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/48/f6375c0a24923beb988f0c71c052604c96641cf43c2d22b91ec1df86afa0/realtime-2.29.0-py3-none-any.whl", hash = "sha256:1a4891e6c82e88ac9d96ac715e435e086f6f8c7665212a8717346de829cbb509", size = 22374, upload-time = "2026-04-24T13:13:01.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6214,7 +6214,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "storage3"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@ -6223,9 +6223,9 @@ dependencies = [
|
||||
{ name = "pyiceberg" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/b5/18df59ba92951d74774eb0265072bf236ead5e3cbc4b802d8bf1cf3581a0/storage3-2.28.3.tar.gz", hash = "sha256:2b3f843cbd44c4a3b483ec076a12c27de88c0ad5358a43067ed44ef08292353f", size = 20109, upload-time = "2026-03-20T14:38:11.467Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/be/771246434b5caf3c6187bfdc932eaede00bf5f2937b47475ab25209ede3e/storage3-2.29.0.tar.gz", hash = "sha256:b0cc2f6714655d725c998d2c5ae8c6fb4f56a513bd31e4f85770df557fe021e3", size = 20160, upload-time = "2026-04-24T13:13:04.626Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/a5/2dbe216954e026a8c2e2dc7dfa5fd7b1a1ae0824d10972e62462f4f15aca/storage3-2.28.3-py3-none-any.whl", hash = "sha256:bac35c5087619174448fdef6a337db4e3dfebf3de69f685bd706de93ddcdad69", size = 28239, upload-time = "2026-03-20T14:38:10.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/c3/790c31866f52c13b26f108b45759bf50dafae3a0bafb4511fadc98ba7c33/storage3-2.29.0-py3-none-any.whl", hash = "sha256:043ef7ff27cc8b9da12be403cf78ee4586180edfcf62b227ff61e1bd79594b06", size = 28284, upload-time = "2026-04-24T13:13:03.338Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6251,7 +6251,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "supabase"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
@ -6262,37 +6262,37 @@ dependencies = [
|
||||
{ name = "supabase-functions" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/98/2f1c95a2269ce995a34f275760b1c2ee71ee7a75649238ca0470afdfc2ef/supabase-2.28.3.tar.gz", hash = "sha256:1200961e46cdec17c7c280a1e09a159544643eada2759591ea69835303a2e1a4", size = 9687, upload-time = "2026-03-20T14:38:13.272Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/a0/2407d616fdf68e8632bbbfb063d1685c38377ac0199e8ca11deaea1f3bf0/supabase-2.29.0.tar.gz", hash = "sha256:a88c4a4eb50fbb903e2e962fbc7c27733b00589140139f9e837bc9fe30dd3615", size = 9689, upload-time = "2026-04-24T13:13:06.728Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/96/1b48eb664153401c22087bbf77f6a428965e830cc8e0d0c6d68324a28342/supabase-2.28.3-py3-none-any.whl", hash = "sha256:52a7ce4a1d2d55fa6d657bf4760672935058143a5bedc64165851be25ce01dbd", size = 16634, upload-time = "2026-03-20T14:38:12.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/52/232f6bbf5326e04ae12e2ef04a24f011a0d7cab379a8b9698652bc8ff78f/supabase-2.29.0-py3-none-any.whl", hash = "sha256:16c3ec4b7094f6b92efc5cd3bb3f96826d3b6dd5d24fe15c89c81166efce88fe", size = 16633, upload-time = "2026-04-24T13:13:05.722Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supabase-auth"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/6f/1bf81293374ba71183b321bf5dfd7151c3db0c2e24715f35783bc1c56385/supabase_auth-2.28.3.tar.gz", hash = "sha256:41c049da82f9d7fc2f111808e57e984015f128d033f58caa67fd76f428472807", size = 39160, upload-time = "2026-03-20T14:38:15.128Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/7f/7ceeb4c7a2caa188062e934897f0e08e1af0a0e47e376c7645c26b4c39d8/supabase_auth-2.29.0.tar.gz", hash = "sha256:46efc6a3455a23957b846dc974303a844ba0413718cfa899425477ac977f95b3", size = 39154, upload-time = "2026-04-24T13:13:08.509Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/d3/e012315aa895b434fa77bc475e2dfeb87119e67918ecca4d88a25f96814d/supabase_auth-2.28.3-py3-none-any.whl", hash = "sha256:e47c5caec7bbf3c258964d027fbbe99f3cc4a956d3a635f898c962b4d22832dd", size = 48378, upload-time = "2026-03-20T14:38:14.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/ac/3c35cf52281f940b9497cf17abfc5c2050ca49f342d60cfafe22dac3482b/supabase_auth-2.29.0-py3-none-any.whl", hash = "sha256:64de6ef8cae80f97d3aa8d5ca507d5427dda5c89885c0bcfe9f8b0263b6fb9a4", size = 48379, upload-time = "2026-04-24T13:13:07.417Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supabase-functions"
|
||||
version = "2.28.3"
|
||||
version = "2.29.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
{ name = "strenum" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/ea/59bf327960e5384fcc9e69afbdf97260a2cf2684a25c0731968a8a393b9c/supabase_functions-2.28.3.tar.gz", hash = "sha256:5a6255d60a263d44251c5ca250fcdde2408a8483a8bf31f4ac80255de8f3fcae", size = 4679, upload-time = "2026-03-20T14:38:16.742Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e5/19/1a1d22749f38f2a6cbca93a6f5a35c9f816c2c3c06bfaa077fa336e90537/supabase_functions-2.29.0.tar.gz", hash = "sha256:0f8a14a2ea9f12b1c208f61dc6f55e2f4b1121f81bf01c08f9b487d22888744d", size = 4683, upload-time = "2026-04-24T13:13:10.432Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ca/1e720f1347a88519e3d52b6d801cd031c3a7a5df66640c5dc6e81d925057/supabase_functions-2.28.3-py3-none-any.whl", hash = "sha256:eb30578866103fed9322c54e95dd68c2f1a4b6b177e129d9369edd364637904e", size = 8801, upload-time = "2026-03-20T14:38:15.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/10/6f8ef0b408ade76b5a439afab588ce5849e9604a23040ca73cfe0b90cb9e/supabase_functions-2.29.0-py3-none-any.whl", hash = "sha256:6f08de52eec5820eae53616868b85e849e181beffaa5d05b8ea1708ceae5e48e", size = 8799, upload-time = "2026-04-24T13:13:09.214Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -2422,21 +2422,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/bucket.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -2525,11 +2515,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/completed/common/summary-status.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/detail/completed/components/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 3
|
||||
@ -2789,11 +2774,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/secret-key/input-copy.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/secret-key/secret-key-generate.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3159,16 +3139,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/base/badges/icon-with-tooltip.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/base/key-value-item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/card/index.tsx": {
|
||||
"ts/no-non-null-asserted-optional-chain": {
|
||||
"count": 1
|
||||
@ -3328,24 +3298,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-detail-panel/detail-header/components/plugin-source-badge.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-detail-panel/detail-header/hooks/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3544,11 +3501,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/plugin-page/plugin-tasks/components/task-status-indicator.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/plugins/readme-panel/index.tsx": {
|
||||
"react/unsupported-syntax": {
|
||||
"count": 1
|
||||
@ -3822,14 +3774,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/detail/content.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/tools/mcp/detail/tool-item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -5394,14 +5338,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/panel/chat-variable-panel/type.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
|
||||
@ -231,10 +231,8 @@ describe('Select wrappers', () => {
|
||||
</Select>,
|
||||
)
|
||||
|
||||
screen.getByRole('group', { name: 'select positioner' }).element().dispatchEvent(new MouseEvent('mouseover', {
|
||||
bubbles: true,
|
||||
}))
|
||||
asHTMLElement(screen.getByRole('dialog', { name: 'select popup' }).element()).click()
|
||||
await screen.getByRole('group', { name: 'select positioner' }).hover()
|
||||
await screen.getByRole('dialog', { name: 'select popup' }).click()
|
||||
screen.getByRole('listbox', { name: 'select list' }).element().dispatchEvent(new FocusEvent('focusin', {
|
||||
bubbles: true,
|
||||
}))
|
||||
|
||||
@ -2,18 +2,10 @@ import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import Header from '../header'
|
||||
|
||||
vi.mock('@langgenius/dify-ui/button', () => ({
|
||||
Button: ({ children }: { children: React.ReactNode }) => <button>{children}</button>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/divider', () => ({
|
||||
default: () => <span data-testid="divider" />,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div data-testid="tooltip">{children}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../credential-selector', () => ({
|
||||
default: () => <div data-testid="credential-selector" />,
|
||||
}))
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type { CredentialSelectorProps } from './credential-selector'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import CredentialSelector from './credential-selector'
|
||||
|
||||
type HeaderProps = {
|
||||
@ -22,6 +21,7 @@ const Header = ({
|
||||
...rest
|
||||
}: HeaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const configurationTip = t('configurationTip', { ns: 'datasetPipeline', pluginName })
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
@ -30,20 +30,23 @@ const Header = ({
|
||||
{...rest}
|
||||
/>
|
||||
<Divider type="vertical" className="mx-1 h-3.5 shrink-0" />
|
||||
<Tooltip
|
||||
popupContent={t('configurationTip', { ns: 'datasetPipeline', pluginName })}
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="size-6 shrink-0 px-1"
|
||||
>
|
||||
<RiEqualizer2Line
|
||||
className="h-4 w-4"
|
||||
onClick={onClickConfiguration}
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="size-6 shrink-0 px-1"
|
||||
aria-label={configurationTip}
|
||||
onClick={onClickConfiguration}
|
||||
>
|
||||
<span aria-hidden className="i-ri-equalizer-2-line h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{configurationTip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<a
|
||||
@ -52,7 +55,7 @@ const Header = ({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<RiBookOpenLine className="size-3.5 shrink-0" />
|
||||
<span aria-hidden className="i-ri-book-open-line size-3.5 shrink-0" />
|
||||
<span title={docTitle}>{docTitle}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -5,9 +5,6 @@ import Bucket from '../bucket'
|
||||
vi.mock('@/app/components/base/icons/src/public/knowledge/online-drive', () => ({
|
||||
BucketsGray: (props: React.SVGProps<SVGSVGElement>) => <svg data-testid="buckets-gray" {...props} />,
|
||||
}))
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children }: { children?: React.ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
describe('Bucket', () => {
|
||||
const defaultProps = {
|
||||
@ -32,8 +29,7 @@ describe('Bucket', () => {
|
||||
|
||||
it('should call handleBackToBucketList on icon button click', () => {
|
||||
render(<Bucket {...defaultProps} />)
|
||||
const buttons = screen.getAllByRole('button')
|
||||
fireEvent.click(buttons[0]!)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'datasetPipeline.onlineDrive.breadcrumbs.allBuckets' }))
|
||||
expect(defaultProps.handleBackToBucketList).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BucketsGray } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type BucketProps = {
|
||||
bucketName: string
|
||||
@ -27,19 +28,28 @@ const Bucket = ({
|
||||
if (!disabled)
|
||||
handleClickBucketName()
|
||||
}, [disabled, handleClickBucketName])
|
||||
const allBucketsLabel = t('onlineDrive.breadcrumbs.allBuckets', { ns: 'datasetPipeline' })
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
popupContent={t('onlineDrive.breadcrumbs.allBuckets', { ns: 'datasetPipeline' })}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover"
|
||||
onClick={handleBackToBucketList}
|
||||
>
|
||||
<BucketsGray />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
aria-label={allBucketsLabel}
|
||||
className="size-6 shrink-0 rounded-md px-0 hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={handleBackToBucketList}
|
||||
>
|
||||
<BucketsGray aria-hidden />
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{allBucketsLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className="system-xs-regular text-divider-deep">/</span>
|
||||
<button
|
||||
|
||||
@ -9,9 +9,6 @@ vi.mock('@/app/components/base/badge', () => ({
|
||||
vi.mock('@/app/components/base/icons/src/vender/knowledge', () => ({
|
||||
SearchLinesSparkle: (props: React.SVGProps<SVGSVGElement>) => <svg data-testid="sparkle-icon" {...props} />,
|
||||
}))
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
describe('SummaryStatus', () => {
|
||||
it('should render badge for SUMMARIZING status', () => {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { SearchLinesSparkle } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type SummaryStatusProps = {
|
||||
status: string
|
||||
@ -18,18 +18,22 @@ const SummaryStatus = ({ status }: SummaryStatusProps) => {
|
||||
return ''
|
||||
}, [status, t])
|
||||
|
||||
if (status !== 'SUMMARIZING')
|
||||
return null
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={tip}
|
||||
>
|
||||
{
|
||||
status === 'SUMMARIZING' && (
|
||||
<Badge className="border-text-accent-secondary text-text-accent-secondary">
|
||||
<SearchLinesSparkle className="mr-0.5 h-3 w-3" />
|
||||
<span>{t('list.summary.generating', { ns: 'datasetDocuments' })}</span>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex">
|
||||
<Badge className="border-text-accent-secondary text-text-accent-secondary">
|
||||
<SearchLinesSparkle aria-hidden className="mr-0.5 h-3 w-3" />
|
||||
<span>{t('list.summary.generating', { ns: 'datasetDocuments' })}</span>
|
||||
</Badge>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{tip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ describe('InputCopy', () => {
|
||||
|
||||
it('should render with empty value by default', async () => {
|
||||
await renderAndFlush(<InputCopy />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should render children when provided', async () => {
|
||||
@ -273,12 +273,12 @@ describe('InputCopy', () => {
|
||||
describe('edge cases', () => {
|
||||
it('should handle undefined value', async () => {
|
||||
await renderAndFlush(<InputCopy value={undefined} />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should handle empty string value', async () => {
|
||||
await renderAndFlush(<InputCopy value="" />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should handle very long values', async () => {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { t } from 'i18next'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { writeTextToClipboard } from '@/utils/clipboard'
|
||||
|
||||
type IInputCopyProps = {
|
||||
@ -18,6 +18,12 @@ const InputCopy = ({
|
||||
children,
|
||||
}: IInputCopyProps) => {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const copyLabel = isCopied ? `${t('copied', { ns: 'appApi' })}` : `${t('copy', { ns: 'appApi' })}`
|
||||
const handleCopy = () => {
|
||||
writeTextToClipboard(value).then(() => {
|
||||
setIsCopied(true)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isCopied) {
|
||||
@ -38,17 +44,24 @@ const InputCopy = ({
|
||||
<div className="relative h-full grow text-[13px]">
|
||||
<div
|
||||
className="r-0 absolute top-0 left-0 w-full cursor-pointer truncate pr-2 pl-2"
|
||||
onClick={() => {
|
||||
writeTextToClipboard(value).then(() => {
|
||||
setIsCopied(true)
|
||||
})
|
||||
role="button"
|
||||
aria-label={copyLabel}
|
||||
tabIndex={0}
|
||||
onClick={handleCopy}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
handleCopy()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
popupContent={isCopied ? `${t('copied', { ns: 'appApi' })}` : `${t('copy', { ns: 'appApi' })}`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="text-text-secondary">{value}</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={<span className="text-text-secondary">{value}</span>}
|
||||
/>
|
||||
<TooltipContent placement="bottom">
|
||||
{copyLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,15 +6,13 @@ vi.mock('../../../base/icons/src/vender/line/files', () => ({
|
||||
CopyCheck: () => <span data-testid="copy-check-icon" />,
|
||||
}))
|
||||
|
||||
vi.mock('../../../base/tooltip', () => ({
|
||||
default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => (
|
||||
<div data-testid="tooltip" data-content={popupContent}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/action-button', () => ({
|
||||
default: ({ children, onClick }: { children: React.ReactNode, onClick: () => void }) => (
|
||||
<button data-testid="action-button" onClick={onClick}>{children}</button>
|
||||
default: ({
|
||||
children,
|
||||
onClick,
|
||||
...props
|
||||
}: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
|
||||
<button data-testid="action-button" onClick={onClick} {...props}>{children}</button>
|
||||
),
|
||||
}))
|
||||
|
||||
@ -54,6 +52,6 @@ describe('KeyValueItem', () => {
|
||||
|
||||
it('renders copy tooltip', () => {
|
||||
render(<KeyValueItem label="ID" value="123" />)
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'common.operation.copy')
|
||||
expect(screen.getByRole('button', { name: 'common.operation.copy' })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,24 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Theme } from '@/types/app'
|
||||
import IconWithTooltip from '../icon-with-tooltip'
|
||||
|
||||
// Mock Tooltip component
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({
|
||||
children,
|
||||
popupContent,
|
||||
popupClassName,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
popupContent?: string
|
||||
popupClassName?: string
|
||||
}) => (
|
||||
<div data-testid="tooltip" data-popup-content={popupContent} data-popup-classname={popupClassName}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock icon components
|
||||
const MockLightIcon = ({ className }: { className?: string }) => (
|
||||
<div data-testid="light-icon" className={className}>Light Icon</div>
|
||||
)
|
||||
@ -44,10 +26,10 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('light-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Tooltip wrapper', () => {
|
||||
it('should render tooltip trigger with accessible label when popupContent is provided', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
@ -57,21 +39,7 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', 'Test tooltip')
|
||||
})
|
||||
|
||||
it('should apply correct popupClassName to Tooltip', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toHaveAttribute('data-popup-classname')
|
||||
expect(tooltip.getAttribute('data-popup-classname')).toContain('border-components-panel-border')
|
||||
expect(screen.getByLabelText('Test tooltip')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -171,10 +139,7 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-popup-content',
|
||||
'Custom tooltip content',
|
||||
)
|
||||
expect(screen.getByLabelText('Custom tooltip content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined popupContent', () => {
|
||||
@ -186,7 +151,7 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('light-icon')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -239,7 +204,7 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', longContent)
|
||||
expect(screen.getByLabelText(longContent)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle special characters in popupContent', () => {
|
||||
@ -253,7 +218,7 @@ describe('IconWithTooltip', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', specialContent)
|
||||
expect(screen.getByLabelText(specialContent)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Theme } from '@/types/app'
|
||||
|
||||
type IconWithTooltipProps = {
|
||||
@ -22,15 +22,24 @@ const IconWithTooltip: FC<IconWithTooltipProps> = ({
|
||||
const isDark = theme === Theme.dark
|
||||
const iconClassName = cn('h-5 w-5', className)
|
||||
const Icon = isDark ? BadgeIconDark : BadgeIconLight
|
||||
const icon = (
|
||||
<span
|
||||
aria-label={popupContent}
|
||||
className="flex shrink-0 items-center justify-center"
|
||||
>
|
||||
<Icon className={iconClassName} />
|
||||
</span>
|
||||
)
|
||||
|
||||
if (!popupContent)
|
||||
return icon
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupClassName="p-1.5 border-[0.5px] border-[0.5px] border-components-panel-border bg-components-tooltip-bg text-text-secondary system-xs-medium"
|
||||
popupContent={popupContent}
|
||||
>
|
||||
<div className="flex shrink-0 items-center justify-center">
|
||||
<Icon className={iconClassName} />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={icon} />
|
||||
<TooltipContent className="border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 system-xs-medium text-text-secondary">
|
||||
{popupContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiClipboardLine,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { CopyCheck } from '../../base/icons/src/vender/line/files'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
@ -45,7 +42,7 @@ const KeyValueItem: FC<Props> = ({
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const CopyIcon = isCopied ? CopyCheck : RiClipboardLine
|
||||
const copyLabel = t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
@ -54,10 +51,19 @@ const KeyValueItem: FC<Props> = ({
|
||||
<span className={cn(valueMaxWidthClassName, 'truncate system-xs-medium text-text-secondary')}>
|
||||
{maskedValue || value}
|
||||
</span>
|
||||
<Tooltip popupContent={t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })} position="top">
|
||||
<ActionButton onClick={handleCopy}>
|
||||
<CopyIcon className="h-3.5 w-3.5 shrink-0 text-text-tertiary" />
|
||||
</ActionButton>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<ActionButton aria-label={copyLabel} onClick={handleCopy}>
|
||||
{isCopied
|
||||
? <CopyCheck aria-hidden className="h-3.5 w-3.5 shrink-0 text-text-tertiary" />
|
||||
: <span aria-hidden className="i-ri-clipboard-line h-3.5 w-3.5 shrink-0 text-text-tertiary" />}
|
||||
</ActionButton>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent placement="top">
|
||||
{copyLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,14 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { PluginSource } from '../../../../types'
|
||||
import PluginSourceBadge from '../plugin-source-badge'
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => (
|
||||
<div data-testid="tooltip" data-content={popupContent}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('PluginSourceBadge', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -20,33 +12,25 @@ describe('PluginSourceBadge', () => {
|
||||
it('should render marketplace source badge', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.marketplace} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toBeInTheDocument()
|
||||
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.marketplace')
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.marketplace')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render github source badge', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.github} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toBeInTheDocument()
|
||||
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.github')
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.github')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render local source badge', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.local} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toBeInTheDocument()
|
||||
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.local')
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.local')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render debugging source badge', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.debugging} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toBeInTheDocument()
|
||||
expect(tooltip).toHaveAttribute('data-content', 'plugin.detailPanel.categoryTip.debugging')
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.debugging')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -86,71 +70,47 @@ describe('PluginSourceBadge', () => {
|
||||
it('should show marketplace tooltip', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.marketplace} />)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-content',
|
||||
'plugin.detailPanel.categoryTip.marketplace',
|
||||
)
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.marketplace')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show github tooltip', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.github} />)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-content',
|
||||
'plugin.detailPanel.categoryTip.github',
|
||||
)
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.github')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show local tooltip', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.local} />)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-content',
|
||||
'plugin.detailPanel.categoryTip.local',
|
||||
)
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.local')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show debugging tooltip', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.debugging} />)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-content',
|
||||
'plugin.detailPanel.categoryTip.debugging',
|
||||
)
|
||||
expect(screen.getByLabelText('plugin.detailPanel.categoryTip.debugging')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Icon Element Structure', () => {
|
||||
it('should render icon inside tooltip for marketplace', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.marketplace} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
const iconWrapper = tooltip.querySelector('div')
|
||||
expect(iconWrapper).toBeInTheDocument()
|
||||
const { container } = render(<PluginSourceBadge source={PluginSource.marketplace} />)
|
||||
expect(container.querySelector('[aria-label="plugin.detailPanel.categoryTip.marketplace"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render icon inside tooltip for github', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.github} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
const iconWrapper = tooltip.querySelector('div')
|
||||
expect(iconWrapper).toBeInTheDocument()
|
||||
const { container } = render(<PluginSourceBadge source={PluginSource.github} />)
|
||||
expect(container.querySelector('[aria-label="plugin.detailPanel.categoryTip.github"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render icon inside tooltip for local', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.local} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
const iconWrapper = tooltip.querySelector('div')
|
||||
expect(iconWrapper).toBeInTheDocument()
|
||||
const { container } = render(<PluginSourceBadge source={PluginSource.local} />)
|
||||
expect(container.querySelector('[aria-label="plugin.detailPanel.categoryTip.local"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render icon inside tooltip for debugging', () => {
|
||||
render(<PluginSourceBadge source={PluginSource.debugging} />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
const iconWrapper = tooltip.querySelector('div')
|
||||
expect(iconWrapper).toBeInTheDocument()
|
||||
const { container } = render(<PluginSourceBadge source={PluginSource.debugging} />)
|
||||
expect(container.querySelector('[aria-label="plugin.detailPanel.categoryTip.debugging"]')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -188,7 +148,7 @@ describe('PluginSourceBadge', () => {
|
||||
const invalidSource = '' as PluginSource
|
||||
render(<PluginSourceBadge source={invalidSource} />)
|
||||
|
||||
expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText(/^plugin\.detailPanel\.categoryTip\./)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import {
|
||||
RiBugLine,
|
||||
RiHardDrive3Line,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Github } from '@/app/components/base/icons/src/public/common'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { PluginSource } from '../../../types'
|
||||
|
||||
type SourceConfig = {
|
||||
@ -30,11 +26,11 @@ const SOURCE_CONFIG_MAP: Record<PluginSource, SourceConfig | null> = {
|
||||
tipKey: 'detailPanel.categoryTip.github',
|
||||
},
|
||||
[PluginSource.local]: {
|
||||
icon: <RiHardDrive3Line className="h-3.5 w-3.5 text-text-tertiary" />,
|
||||
icon: <span aria-hidden className="i-ri-hard-drive-3-line h-3.5 w-3.5 text-text-tertiary" />,
|
||||
tipKey: 'detailPanel.categoryTip.local',
|
||||
},
|
||||
[PluginSource.debugging]: {
|
||||
icon: <RiBugLine className="h-3.5 w-3.5 text-text-tertiary hover:text-text-warning" />,
|
||||
icon: <span aria-hidden className="i-ri-bug-line h-3.5 w-3.5 text-text-tertiary hover:text-text-warning" />,
|
||||
tipKey: 'detailPanel.categoryTip.debugging',
|
||||
},
|
||||
}
|
||||
@ -45,12 +41,22 @@ const PluginSourceBadge: FC<PluginSourceBadgeProps> = ({ source }) => {
|
||||
const config = SOURCE_CONFIG_MAP[source]
|
||||
if (!config)
|
||||
return null
|
||||
const tip = t(config.tipKey as never, { ns: 'plugin' })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mr-0.5 ml-1 system-xs-regular text-text-quaternary">·</div>
|
||||
<Tooltip popupContent={t(config.tipKey as never, { ns: 'plugin' })}>
|
||||
<div>{config.icon}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span aria-label={tip} className="inline-flex">
|
||||
{config.icon}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{tip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { EndpointListItem, PluginDetail } from '../types'
|
||||
import {
|
||||
AlertDialog,
|
||||
@ -9,7 +10,7 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiClipboardLine, RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import * as React from 'react'
|
||||
@ -17,7 +18,6 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import {
|
||||
@ -29,6 +29,8 @@ import {
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import { NAME_FIELD } from './utils'
|
||||
|
||||
type EndpointModalFormSchemas = ComponentProps<typeof EndpointModal>['formSchemas']
|
||||
|
||||
type Props = {
|
||||
pluginDetail: PluginDetail
|
||||
data: EndpointListItem
|
||||
@ -118,7 +120,7 @@ const EndpointCard = ({
|
||||
toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }))
|
||||
},
|
||||
})
|
||||
const handleUpdate = (state: Record<string, any>) => updateEndpoint({
|
||||
const handleUpdate = (state: Record<string, unknown>) => updateEndpoint({
|
||||
endpointID,
|
||||
state,
|
||||
})
|
||||
@ -148,22 +150,22 @@ const EndpointCard = ({
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const CopyIcon = isCopied ? CopyCheck : RiClipboardLine
|
||||
const copyLabel = t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-background-section-burn p-0.5">
|
||||
<div className="group rounded-[10px] border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-2.5 pl-3">
|
||||
<div className="flex items-center">
|
||||
<div className="mb-1 flex h-6 grow items-center gap-1 system-md-semibold text-text-secondary">
|
||||
<RiLoginCircleLine className="h-4 w-4" />
|
||||
<span aria-hidden className="i-ri-login-circle-line h-4 w-4" />
|
||||
<div>{data.name}</div>
|
||||
</div>
|
||||
<div className="hidden items-center group-hover:flex">
|
||||
<ActionButton onClick={showEndpointModalConfirm}>
|
||||
<RiEditLine className="h-4 w-4" />
|
||||
<span aria-hidden className="i-ri-edit-line h-4 w-4" />
|
||||
</ActionButton>
|
||||
<ActionButton onClick={showDeleteConfirm} className="text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive">
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
<span aria-hidden className="i-ri-delete-bin-line h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,10 +174,23 @@ const EndpointCard = ({
|
||||
<div className="w-12 shrink-0 system-xs-regular text-text-tertiary">{endpoint.method}</div>
|
||||
<div className="group/item flex grow items-center truncate system-xs-regular text-text-secondary">
|
||||
<div title={`${data.url}${endpoint.path}`} className="truncate">{`${data.url}${endpoint.path}`}</div>
|
||||
<Tooltip popupContent={t(`operation.${isCopied ? 'copied' : 'copy'}`, { ns: 'common' })} position="top">
|
||||
<ActionButton className="ml-2 hidden shrink-0 group-hover/item:flex" onClick={() => handleCopy(`${data.url}${endpoint.path}`)}>
|
||||
<CopyIcon className="h-3.5 w-3.5 text-text-tertiary" />
|
||||
</ActionButton>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<ActionButton
|
||||
aria-label={copyLabel}
|
||||
className="ml-2 hidden shrink-0 group-hover/item:flex"
|
||||
onClick={() => handleCopy(`${data.url}${endpoint.path}`)}
|
||||
>
|
||||
{isCopied
|
||||
? <CopyCheck aria-hidden className="h-3.5 w-3.5 text-text-tertiary" />
|
||||
: <span aria-hidden className="i-ri-clipboard-line h-3.5 w-3.5 text-text-tertiary" />}
|
||||
</ActionButton>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent placement="top">
|
||||
{copyLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -244,7 +259,7 @@ const EndpointCard = ({
|
||||
</AlertDialog>
|
||||
{isShowEndpointModal && (
|
||||
<EndpointModal
|
||||
formSchemas={formSchemas as any}
|
||||
formSchemas={formSchemas as EndpointModalFormSchemas}
|
||||
defaultValues={formValue}
|
||||
onCancel={hideEndpointModalConfirm}
|
||||
onSaved={handleUpdate}
|
||||
|
||||
@ -7,12 +7,6 @@ vi.mock('@/app/components/base/progress-bar/progress-circle', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children, popupContent }: { children: React.ReactNode, popupContent: string }) => (
|
||||
<div data-testid="tooltip" data-tip={popupContent}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/plugins-nav/downloading-icon', () => ({
|
||||
default: () => <span data-testid="downloading-icon" />,
|
||||
}))
|
||||
@ -38,18 +32,17 @@ describe('TaskStatusIndicator', () => {
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<TaskStatusIndicator {...defaultProps} />)
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'Installing plugins' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass tip to tooltip', () => {
|
||||
it('should use tip as the trigger accessible name', () => {
|
||||
render(<TaskStatusIndicator {...defaultProps} tip="My tip" />)
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-tip', 'My tip')
|
||||
expect(screen.getByRole('button', { name: 'My tip' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render install icon by default', () => {
|
||||
const { container } = render(<TaskStatusIndicator {...defaultProps} />)
|
||||
// RiInstallLine renders as svg
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
expect(container.querySelector('.i-ri-install-line')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('downloading-icon')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -127,7 +120,6 @@ describe('TaskStatusIndicator', () => {
|
||||
totalPluginsLength={3}
|
||||
/>,
|
||||
)
|
||||
// RiCheckboxCircleFill is rendered as svg with text-text-success
|
||||
const successIcon = container.querySelector('.text-text-success')
|
||||
expect(successIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import type { FC } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningFill,
|
||||
RiInstallLine,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import DownloadingIcon from '@/app/components/header/plugins-nav/downloading-icon'
|
||||
|
||||
type TaskStatusIndicatorProps = {
|
||||
@ -39,56 +35,61 @@ const TaskStatusIndicator: FC<TaskStatusIndicatorProps> = ({
|
||||
const showSuccessIcon = isSuccess || (successPluginsLength > 0 && runningPluginsLength === 0)
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={tip}
|
||||
asChild
|
||||
offset={8}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex h-8 w-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
|
||||
showErrorStyle && 'cursor-pointer border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
|
||||
(isInstalling || isInstallingWithSuccess || isSuccess) && 'cursor-pointer hover:bg-components-button-secondary-bg-hover',
|
||||
)}
|
||||
id="plugin-task-trigger"
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Main Icon */}
|
||||
{showDownloadingIcon
|
||||
? <DownloadingIcon />
|
||||
: (
|
||||
<RiInstallLine
|
||||
className={cn(
|
||||
'h-4 w-4 text-components-button-secondary-text',
|
||||
showErrorStyle && 'text-components-button-destructive-secondary-text',
|
||||
)}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="small"
|
||||
aria-label={tip}
|
||||
className={cn(
|
||||
'relative h-8 w-8 rounded-lg px-0',
|
||||
'focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
showErrorStyle && 'cursor-pointer border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
|
||||
(isInstalling || isInstallingWithSuccess || isSuccess) && 'cursor-pointer hover:bg-components-button-secondary-bg-hover',
|
||||
)}
|
||||
id="plugin-task-trigger"
|
||||
onClick={onClick}
|
||||
>
|
||||
{showDownloadingIcon
|
||||
? <DownloadingIcon />
|
||||
: (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'i-ri-install-line h-4 w-4 text-components-button-secondary-text',
|
||||
showErrorStyle && 'text-components-button-destructive-secondary-text',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Status Indicator Badge */}
|
||||
<div className="absolute -top-1 -right-1">
|
||||
{(isInstalling || isInstallingWithSuccess) && (
|
||||
<ProgressCircle
|
||||
percentage={(totalPluginsLength > 0 ? successPluginsLength / totalPluginsLength : 0) * 100}
|
||||
circleFillColor="fill-components-progress-brand-bg"
|
||||
/>
|
||||
)}
|
||||
{isInstallingWithError && (
|
||||
<ProgressCircle
|
||||
percentage={(totalPluginsLength > 0 ? runningPluginsLength / totalPluginsLength : 0) * 100}
|
||||
circleFillColor="fill-components-progress-brand-bg"
|
||||
sectorFillColor="fill-components-progress-error-border"
|
||||
circleStrokeColor="stroke-components-progress-error-border"
|
||||
/>
|
||||
)}
|
||||
{showSuccessIcon && !isInstalling && !isInstallingWithSuccess && !isInstallingWithError && (
|
||||
<RiCheckboxCircleFill className="h-3.5 w-3.5 text-text-success" />
|
||||
)}
|
||||
{isFailed && (
|
||||
<RiErrorWarningFill className="h-3.5 w-3.5 text-text-destructive" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute -top-1 -right-1">
|
||||
{(isInstalling || isInstallingWithSuccess) && (
|
||||
<ProgressCircle
|
||||
percentage={(totalPluginsLength > 0 ? successPluginsLength / totalPluginsLength : 0) * 100}
|
||||
circleFillColor="fill-components-progress-brand-bg"
|
||||
/>
|
||||
)}
|
||||
{isInstallingWithError && (
|
||||
<ProgressCircle
|
||||
percentage={(totalPluginsLength > 0 ? runningPluginsLength / totalPluginsLength : 0) * 100}
|
||||
circleFillColor="fill-components-progress-brand-bg"
|
||||
sectorFillColor="fill-components-progress-error-border"
|
||||
circleStrokeColor="stroke-components-progress-error-border"
|
||||
/>
|
||||
)}
|
||||
{showSuccessIcon && !isInstalling && !isInstallingWithSuccess && !isInstallingWithError && (
|
||||
<span aria-hidden className="i-ri-checkbox-circle-fill h-3.5 w-3.5 text-text-success" />
|
||||
)}
|
||||
{isFailed && (
|
||||
<span aria-hidden className="i-ri-error-warning-fill h-3.5 w-3.5 text-text-destructive" />
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent sideOffset={8}>{tip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -698,16 +698,9 @@ describe('MCPDetailContent', () => {
|
||||
const onHide = vi.fn()
|
||||
render(<MCPDetailContent {...defaultProps} onHide={onHide} />, { wrapper: createWrapper() })
|
||||
|
||||
// Find the close button (ActionButton with RiCloseLine)
|
||||
const buttons = screen.getAllByRole('button')
|
||||
const closeButton = buttons.find(btn =>
|
||||
btn.querySelector('svg.h-4.w-4'),
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.close' }))
|
||||
|
||||
if (closeButton) {
|
||||
fireEvent.click(closeButton)
|
||||
expect(onHide).toHaveBeenCalled()
|
||||
}
|
||||
expect(onHide).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import type { ToolWithProvider } from '../../../workflow/types'
|
||||
import {
|
||||
AlertDialog,
|
||||
@ -12,18 +12,13 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiLoader2Line,
|
||||
RiLoopLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -49,6 +44,11 @@ type Props = {
|
||||
onFirstCreate: () => void
|
||||
}
|
||||
|
||||
type MCPModalConfirmPayload = Parameters<ComponentProps<typeof MCPModal>['onConfirm']>[0]
|
||||
type MutationResult = {
|
||||
result?: string
|
||||
}
|
||||
|
||||
const MCPDetailContent: FC<Props> = ({
|
||||
detail,
|
||||
onUpdate,
|
||||
@ -128,14 +128,14 @@ const MCPDetailContent: FC<Props> = ({
|
||||
}
|
||||
}, [onFirstCreate, isCurrentWorkspaceManager, detail, authorizeMcp, handleUpdateTools, handleOAuthCallback, onUpdate])
|
||||
|
||||
const handleUpdate = useCallback(async (data: any) => {
|
||||
const handleUpdate = useCallback(async (data: MCPModalConfirmPayload) => {
|
||||
if (!detail)
|
||||
return
|
||||
const res = await updateMCP({
|
||||
...data,
|
||||
provider_id: detail.id,
|
||||
})
|
||||
if ((res as any)?.result === 'success') {
|
||||
}) as MutationResult
|
||||
if (res.result === 'success') {
|
||||
hideUpdateModal()
|
||||
onUpdate()
|
||||
handleAuthorize()
|
||||
@ -146,9 +146,9 @@ const MCPDetailContent: FC<Props> = ({
|
||||
if (!detail)
|
||||
return
|
||||
showDeleting()
|
||||
const res = await deleteMCP(detail.id)
|
||||
const res = await deleteMCP(detail.id) as MutationResult
|
||||
hideDeleting()
|
||||
if ((res as any)?.result === 'success') {
|
||||
if (res.result === 'success') {
|
||||
hideDeleteConfirm()
|
||||
onUpdate(true)
|
||||
}
|
||||
@ -161,6 +161,8 @@ const MCPDetailContent: FC<Props> = ({
|
||||
|
||||
if (!detail)
|
||||
return null
|
||||
const identifierLabel = t('mcp.identifier', { ns: 'tools' })
|
||||
const serverUrlLabel = t('mcp.modal.serverUrl', { ns: 'tools' })
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -174,12 +176,37 @@ const MCPDetailContent: FC<Props> = ({
|
||||
<div className="truncate system-md-semibold text-text-primary" title={detail.name}>{detail.name}</div>
|
||||
</div>
|
||||
<div className="mt-0.5 flex items-center gap-1">
|
||||
<Tooltip popupContent={t('mcp.identifier', { ns: 'tools' })}>
|
||||
<div className="shrink-0 cursor-pointer system-xs-regular text-text-secondary" onClick={() => copy(detail.server_identifier || '')}>{detail.server_identifier}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="small"
|
||||
aria-label={identifierLabel}
|
||||
className="h-auto shrink-0 cursor-pointer rounded bg-transparent p-0 text-left system-xs-regular text-text-secondary hover:bg-transparent focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={() => copy(detail.server_identifier || '')}
|
||||
>
|
||||
{detail.server_identifier}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{identifierLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="shrink-0 system-xs-regular text-text-quaternary">·</div>
|
||||
<Tooltip popupContent={t('mcp.modal.serverUrl', { ns: 'tools' })}>
|
||||
<div className="truncate system-xs-regular text-text-secondary">{detail.server_url}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div aria-label={serverUrlLabel} className="truncate system-xs-regular text-text-secondary">
|
||||
{detail.server_url}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{serverUrlLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -188,8 +215,8 @@ const MCPDetailContent: FC<Props> = ({
|
||||
onEdit={showUpdateModal}
|
||||
onRemove={showDeleteConfirm}
|
||||
/>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
<ActionButton aria-label={t('operation.close', { ns: 'common' })} onClick={onHide}>
|
||||
<span aria-hidden className="i-ri-close-line h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
@ -221,7 +248,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
className="w-full"
|
||||
disabled
|
||||
>
|
||||
<RiLoader2Line className={cn('mr-1 h-4 w-4 animate-spin')} />
|
||||
<span aria-hidden className="mr-1 i-ri-loader-2-line h-4 w-4 animate-spin" />
|
||||
{t('mcp.authorizing', { ns: 'tools' })}
|
||||
</Button>
|
||||
)}
|
||||
@ -262,7 +289,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
</div>
|
||||
<div>
|
||||
<Button size="small" onClick={showUpdateConfirm}>
|
||||
<RiLoopLeftLine className="mr-1 h-3.5 w-3.5" />
|
||||
<span aria-hidden className="mr-1 i-ri-loop-left-line h-3.5 w-3.5" />
|
||||
{t('mcp.update', { ns: 'tools' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -36,8 +36,9 @@ describe('VariableTypeSelector', () => {
|
||||
await user.keyboard('{Escape}')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('number')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('combobox')).toHaveAttribute('aria-expanded', 'false')
|
||||
})
|
||||
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps the custom popup class in in-cell mode', async () => {
|
||||
|
||||
@ -1,38 +1,47 @@
|
||||
'use client'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
type Props = {
|
||||
type Props<T extends string> = {
|
||||
inCell?: boolean
|
||||
value?: any
|
||||
list: any
|
||||
onSelect: (value: any) => void
|
||||
value?: T
|
||||
list: readonly T[]
|
||||
onSelect: (value: T) => void
|
||||
popupClassName?: string
|
||||
}
|
||||
|
||||
const VariableTypeSelector = ({
|
||||
const VariableTypeSelector = <T extends string, >({
|
||||
inCell = false,
|
||||
value,
|
||||
list,
|
||||
onSelect,
|
||||
popupClassName,
|
||||
}: Props) => {
|
||||
}: Props<T>) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleValueChange = (nextValue: string | null) => {
|
||||
if (!nextValue)
|
||||
return
|
||||
|
||||
const nextItem = list.find(item => item === nextValue)
|
||||
if (!nextItem)
|
||||
return
|
||||
|
||||
onSelect(nextItem)
|
||||
}
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
<Select
|
||||
value={value ?? null}
|
||||
open={open}
|
||||
onOpenChange={() => setOpen(v => !v)}
|
||||
placement="bottom"
|
||||
onOpenChange={setOpen}
|
||||
onValueChange={handleValueChange}
|
||||
>
|
||||
<PortalToFollowElemTrigger className="w-full" onClick={() => setOpen(v => !v)}>
|
||||
<SelectTrigger
|
||||
className="h-auto w-full max-w-none cursor-pointer rounded-none bg-transparent p-0 hover:bg-transparent focus-visible:bg-transparent data-popup-open:bg-transparent [&>*:last-child]:hidden"
|
||||
>
|
||||
<div className={cn(
|
||||
'flex w-full cursor-pointer items-center px-2',
|
||||
!inCell && 'rounded-lg bg-components-input-bg-normal py-1 hover:bg-state-base-hover-alt',
|
||||
@ -48,27 +57,22 @@ const VariableTypeSelector = ({
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
<RiArrowDownSLine className="ml-0.5 h-4 w-4 text-text-quaternary" />
|
||||
<span className="ml-0.5 i-ri-arrow-down-s-line h-4 w-4 text-text-quaternary" aria-hidden="true" />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className={cn('z-11 w-full', popupClassName)}>
|
||||
<div className="rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg">
|
||||
{list.map((item: any) => (
|
||||
<div
|
||||
key={item}
|
||||
className="flex cursor-pointer items-center gap-2 rounded-lg py-[6px] pr-2 pl-3 hover:bg-state-base-hover"
|
||||
onClick={() => {
|
||||
onSelect(item)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className="grow truncate system-md-regular text-text-secondary">{item}</div>
|
||||
{value === item && <RiCheckLine className="h-4 w-4 text-text-accent" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</SelectTrigger>
|
||||
<SelectContent placement="bottom-start" popupClassName={cn('bg-components-panel-bg-blur', popupClassName)}>
|
||||
{list.map(item => (
|
||||
<SelectItem
|
||||
key={item}
|
||||
value={item}
|
||||
className="h-auto gap-2 py-[6px] pr-2 pl-3 system-md-regular font-normal"
|
||||
>
|
||||
<SelectItemText className="px-0 system-md-regular text-text-secondary">{item}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user