feat(web): Index/model/space/tool ui upgrade
18
web/src/assets/images/common/arrow_right_dark.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 5</title>
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1285, -192)" stroke="#171719">
|
||||
<g id="新手指引" transform="translate(1100, 63)">
|
||||
<g id="编组-44" transform="translate(12, 121)">
|
||||
<g id="编组-37" transform="translate(99, 7)">
|
||||
<g id="编组-5" transform="translate(74, 1)">
|
||||
<polyline id="路径" points="10 6 12 8 10 10"></polyline>
|
||||
<line x1="12" y1="8" x2="2" y2="8" id="路径-2"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 962 B |
30
web/src/assets/images/common/delete_red.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 33</title>
|
||||
<defs>
|
||||
<filter x="-2.2%" y="-5.4%" width="104.5%" height="112.7%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="4" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0.0901960784 0 0 0 0 0.0901960784 0 0 0 0 0.0980392157 0 0 0 0.16 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="记忆管理" transform="translate(-804, -283)" stroke="#FF5D34">
|
||||
<g id="1备份-2" filter="url(#filter-1)" transform="translate(252, 108)">
|
||||
<g id="编组-33" transform="translate(552, 175)">
|
||||
<g id="编组-32" transform="translate(3.5, 4)">
|
||||
<line x1="-1.80133686e-14" y1="2.22222222" x2="11" y2="2.22222222" id="路径-29"></line>
|
||||
<polyline id="路径-30" stroke-linejoin="round" points="3.3 2.2221179 3.3 0 7.7 0 7.7 2.22222222"></polyline>
|
||||
<path d="M1.65,2.23587458 L1.65,9 C1.65,9.55228475 2.09771525,10 2.65,10 L8.35,10 C8.90228475,10 9.35,9.55228475 9.35,9 L9.35,2.22222222 L9.35,2.22222222" id="路径-31" stroke-linejoin="round"></path>
|
||||
<line x1="4.4" y1="4.45203738" x2="4.4" y2="7.78537071" id="路径-32"></line>
|
||||
<line x1="6.6" y1="4.45203738" x2="6.6" y2="7.78537071" id="路径-32"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
20
web/src/assets/images/common/global_outline.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>互联网</title>
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="平台管理-工具管理--工具市场" transform="translate(-1348, -329)" stroke="#5F6266">
|
||||
<g id="卡片1备份-2" transform="translate(1044, 176)">
|
||||
<g id="编组-5" transform="translate(16, 152)">
|
||||
<g id="互联网" transform="translate(288, 1)">
|
||||
<g id="编组-11" transform="translate(2, 2)">
|
||||
<circle id="椭圆形" cx="6" cy="6" r="5.5"></circle>
|
||||
<line x1="0.857142857" y1="3.85714286" x2="11.1428571" y2="3.85714286" id="路径-6"></line>
|
||||
<path d="M4.66132498,0.645258919 C4.17108437,1.90189554 3.85714286,3.83362782 3.85714286,6 C3.85714286,8.18013114 4.17508482,10.1226325 4.67068706,11.3786026 M7.26566129,11.533836 C7.79752775,10.2860801 8.14285714,8.27208968 8.14285714,6 C8.14285714,3.77681214 7.81223272,1.80073299 7.2997423,0.54778953" id="形状"></path>
|
||||
<line x1="0.843017916" y1="7.71428571" x2="11.1287322" y2="7.71428571" id="路径-6"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
web/src/assets/images/common/plus_grey.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>形状结合@2x</title>
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-模型管理-模型广场" transform="translate(-999, -169)" stroke="#A8A9AA" stroke-width="1.2">
|
||||
<g id="卡片1备份" transform="translate(648, 146)">
|
||||
<g id="编组-5" transform="translate(344, 16)">
|
||||
<path d="M7,12 L17,12 M12,7 L12,17" id="形状结合"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 723 B |
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 34</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-1056, -232)">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份-3" transform="translate(648, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1040, -196)">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份-3" transform="translate(636, 0)">
|
||||
<g id="编组-34" transform="translate(152, 16)">
|
||||
<rect id="矩形" fill="#9C6FFF" x="0" y="0" width="32" height="32" rx="8"></rect>
|
||||
<g id="icon-GIS_运行管理" transform="translate(7, 8)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<path d="M7.16762406,14.1962904 C6.34809084,14.1646209 5.54326395,13.9741465 4.80020028,13.636004 C3.71131238,13.1260904 3.00897969,12.3104053 2.71316514,11.2234143 L2.87831314,11.2234143 C3.04376149,11.2238267 3.19695148,11.1385171 3.28050226,10.9994401 C3.36405304,10.8603631 3.3653563,10.6885064 3.28392388,10.5482427 L2.07888794,8.54040242 C1.99542259,8.40040876 1.84176478,8.31425552 1.67554571,8.31425552 C1.50932664,8.31425552 1.35566883,8.40040876 1.27220348,8.54040242 L0.0617231013,10.5500101 C-0.0210158141,10.6903056 -0.0205423886,10.8629501 0.0629647773,11.0028132 C0.146471943,11.1426763 0.300303569,11.2284717 0.466426438,11.227833 L0.804889093,11.227833 C1.23681463,13.5308399 2.77123916,14.6911806 3.98534917,15.2567694 C4.98325891,15.7122175 6.06611522,15.9645167 7.16762406,15.998225 C7.68009348,15.9996891 8.09703053,15.596793 8.09953973,15.0976976 C8.100628,14.8580057 8.00249628,14.627997 7.82730865,14.4596427 C7.65206093,14.2900046 7.4147074,14.1952508 7.16762406,14.1962904 L7.16762406,14.1962904 Z M17.9430772,5.00547242 C17.8606071,4.86230637 17.7051137,4.77374302 17.5365591,4.77393451 L17.1980964,4.77393451 C16.7661709,2.47092761 15.230839,1.31058684 14.0176364,0.742346841 C13.0197435,0.28685171 11.9368771,0.0345501233 10.8353615,0.000895273713 C10.4982732,-0.0093928287 10.18217,0.159876081 10.0105024,0.442595865 C9.83883478,0.725315649 9.83883478,1.076638 10.0105024,1.35935779 C10.18217,1.64207757 10.4982732,1.81134648 10.8353615,1.80105838 C11.6548676,1.83493183 12.4595539,2.02529996 13.2036927,2.36134478 C14.2925806,2.87214217 14.9949133,3.68694353 15.2907278,4.77393451 L15.1255798,4.77393451 C14.9574006,4.77106293 14.8007624,4.85694697 14.7160555,4.99847492 C14.6313487,5.14000288 14.6318043,5.3150681 14.7172468,5.456176 L15.9213754,7.46401624 C16.0048407,7.60400991 16.1584985,7.69016315 16.3247176,7.69016315 C16.4909367,7.69016315 16.6445945,7.60400991 16.7280598,7.46401624 L17.9303735,5.4588272 C18.018434,5.32105318 18.023291,5.14771891 17.9430772,5.00547242 L17.9430772,5.00547242 Z M16.1563937,7.99424995 L10.1375658,7.99424995 C9.50717772,7.99229725 8.99410285,8.48763753 8.99060389,9.10157256 L8.99060389,14.8926716 C8.99410285,15.5066067 9.50717772,16.0019469 10.1375658,15.9999942 L16.1563937,15.9999942 C16.7867818,16.0019469 17.2998566,15.5066067 17.3033556,14.8926716 L17.3033556,9.10157256 C17.2998566,8.48763753 16.7867818,7.99229725 16.1563937,7.99424995 L16.1563937,7.99424995 Z M15.4431721,9.79530257 L15.4431721,14.1962904 L10.854417,14.1962904 L10.854417,9.79530257 L15.4431721,9.79530257 Z M9.00058536,1.10732836 C8.99708639,0.493393328 8.48401152,-0.0019469461 7.85362344,5.75355807e-06 L1.83207335,5.75355807e-06 C1.20203901,-0.00145886726 0.689517223,0.493739212 0.686018831,1.10732836 L0.686018831,6.89842744 C0.686499539,7.19390032 0.807543503,7.47707233 1.02249671,7.68558868 C1.23744991,7.89410503 1.5286856,8.0108668 1.83207335,8.01018014 L7.85090122,8.01018014 C8.15491658,8.01180822 8.44708623,7.89546249 8.66282574,7.68684408 C8.87856526,7.47822567 9.00010748,7.1945153 9.00058536,6.89842744 L9.00058536,1.10732836 Z M7.13677223,1.80105838 L7.13677223,6.20558116 L2.54983195,6.20558116 L2.54983195,1.80105838 L7.13677223,1.80105838 Z" id="形状"></path>
|
||||
<path d="M7.16762406,14.1962904 C6.34809084,14.1646209 5.54326395,13.9741465 4.80020028,13.636004 C3.71131238,13.1260904 3.00897969,12.3104053 2.71316514,11.2234143 L2.87831314,11.2234143 C3.04376149,11.2238267 3.19695148,11.1385171 3.28050226,10.9994401 C3.36405304,10.8603631 3.3653563,10.6885064 3.28392388,10.5482427 L2.07888794,8.54040242 C1.99542259,8.40040876 1.84176478,8.31425552 1.67554571,8.31425552 C1.50932664,8.31425552 1.35566883,8.40040876 1.27220348,8.54040242 L0.0617231013,10.5500101 C-0.0210158141,10.6903056 -0.0205423886,10.8629501 0.0629647773,11.0028132 C0.146471943,11.1426763 0.300303569,11.2284717 0.466426438,11.227833 L0.804889093,11.227833 C1.23681463,13.5308399 2.77123916,14.6911806 3.98534917,15.2567694 C4.98325891,15.7122175 6.06611522,15.9645167 7.16762406,15.998225 C7.68009348,15.9996891 8.09703053,15.596793 8.09953973,15.0976976 C8.100628,14.8580057 8.00249628,14.627997 7.82730865,14.4596427 C7.65206093,14.2900046 7.4147074,14.1952508 7.16762406,14.1962904 L7.16762406,14.1962904 Z M17.9430772,5.00547242 C17.8606071,4.86230637 17.7051137,4.77374302 17.5365591,4.77393451 L17.1980964,4.77393451 C16.7661709,2.47092761 15.230839,1.31058684 14.0176364,0.742346841 C13.0197435,0.28685171 11.9368771,0.0345501233 10.8353615,0.000895273713 C10.4982732,-0.0093928287 10.18217,0.159876081 10.0105024,0.442595865 C9.83883478,0.725315649 9.83883478,1.076638 10.0105024,1.35935779 C10.18217,1.64207757 10.4982732,1.81134648 10.8353615,1.80105838 C11.6548676,1.83493183 12.4595539,2.02529996 13.2036927,2.36134478 C14.2925806,2.87214217 14.9949133,3.68694353 15.2907278,4.77393451 L15.1255798,4.77393451 C14.9574006,4.77106293 14.8007624,4.85694697 14.7160555,4.99847492 C14.6313487,5.14000288 14.6318043,5.3150681 14.7172468,5.456176 L15.9213754,7.46401624 C16.0048407,7.60400991 16.1584985,7.69016315 16.3247176,7.69016315 C16.4909367,7.69016315 16.6445945,7.60400991 16.7280598,7.46401624 L17.9303735,5.4588272 C18.018434,5.32105318 18.023291,5.14771891 17.9430772,5.00547242 L17.9430772,5.00547242 Z M16.1563937,7.99424995 L10.1375658,7.99424995 C9.50717772,7.99229725 8.99410285,8.48763753 8.99060389,9.10157256 L8.99060389,14.8926716 C8.99410285,15.5066067 9.50717772,16.0019469 10.1375658,15.9999942 L16.1563937,15.9999942 C16.7867818,16.0019469 17.2998566,15.5066067 17.3033556,14.8926716 L17.3033556,9.10157256 C17.2998566,8.48763753 16.7867818,7.99229725 16.1563937,7.99424995 L16.1563937,7.99424995 Z M15.4431721,9.79530257 L15.4431721,14.1962904 L10.854417,14.1962904 L10.854417,9.79530257 L15.4431721,9.79530257 Z M9.00058536,1.10732836 C8.99708639,0.493393328 8.48401152,-0.0019469461 7.85362344,5.75355757e-06 L1.83207335,5.75355757e-06 C1.20203901,-0.00145886726 0.689517223,0.493739212 0.686018831,1.10732836 L0.686018831,6.89842744 C0.686499539,7.19390032 0.807543503,7.47707233 1.02249671,7.68558868 C1.23744991,7.89410503 1.5286856,8.0108668 1.83207335,8.01018014 L7.85090122,8.01018014 C8.15491658,8.01180822 8.44708623,7.89546249 8.66282574,7.68684408 C8.87856526,7.47822567 9.00010748,7.1945153 9.00058536,6.89842744 L9.00058536,1.10732836 Z M7.13677223,1.80105838 L7.13677223,6.20558116 L2.54983195,6.20558116 L2.54983195,1.80105838 L7.13677223,1.80105838 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>箭头_向上</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-928, -367)" fill="#FF5D34" fill-rule="nonzero">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份-3" transform="translate(648, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-912, -331)" fill="#FF5D34" fill-rule="nonzero">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份-3" transform="translate(636, 0)">
|
||||
<g id="标签1" transform="translate(16, 148)">
|
||||
<g id="箭头_向上" transform="translate(15, 10) scale(1, -1) translate(-15, -10)translate(8, 3)">
|
||||
<polygon id="路径" points="7.58333789 4.21666211 7.58333789 12.4416621 6.41666211 12.4416621 6.41666211 4.21666211 4.60833789 6.025 3.79166211 5.20832422 7 2 10.2083379 5.20832422 9.39166211 6.025"></polygon>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 30</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="首页" transform="translate(-1006, -326)" stroke="#FF5D34" stroke-width="1.2">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份-3" transform="translate(648, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="工作台-首页" transform="translate(-990, -290)" stroke="#FF5D34" stroke-width="1.2">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份-3" transform="translate(636, 0)">
|
||||
<g id="编组-30" transform="translate(108, 116) scale(1, -1) translate(-108, -116)translate(102, 110)">
|
||||
<g id="编组-31" transform="translate(2.5, 2.5)">
|
||||
<polyline id="路径" points="0 3 3.5 0 7 3"></polyline>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>箭头_向上</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-712, -367)" fill="#369F21" fill-rule="nonzero">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份-2" transform="translate(432, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-700, -331)" fill="#369F21" fill-rule="nonzero">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份-2" transform="translate(424, 0)">
|
||||
<g id="标签1" transform="translate(16, 148)">
|
||||
<g id="箭头_向上" transform="translate(8, 3)">
|
||||
<polygon id="路径" points="7.58333789 4.21666211 7.58333789 12.4416621 6.41666211 12.4416621 6.41666211 4.21666211 4.60833789 6.025 3.79166211 5.20832422 7 2 10.2083379 5.20832422 9.39166211 6.025"></polygon>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 30</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="首页" transform="translate(-488, -326)" stroke="#369F21" stroke-width="1.2">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份" transform="translate(216, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="工作台-首页" transform="translate(-480, -290)" stroke="#369F21" stroke-width="1.2">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份" transform="translate(212, 0)">
|
||||
<g id="编组-30" transform="translate(16, 110)">
|
||||
<g id="编组-31" transform="translate(2.5, 2.5)">
|
||||
<polyline id="路径" points="0 3 3.5 0 7 3"></polyline>
|
||||
|
||||
|
Before Width: | Height: | Size: 1013 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 17</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-1252, -772)">
|
||||
<g id="快捷操作" transform="translate(1120, 502)">
|
||||
<g id="编组-11" transform="translate(2, 38)">
|
||||
<g id="编组-17" transform="translate(130, 232)">
|
||||
<rect id="矩形" stroke="#DFE4ED" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="8"></rect>
|
||||
<g id="使用帮助" transform="translate(8, 8)" stroke="#212332" stroke-width="1.7">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1136, -700)">
|
||||
<g id="快捷操作" transform="translate(1100, 554)">
|
||||
<g id="编组-11" transform="translate(2, 34)">
|
||||
<g id="编组-17" transform="translate(34, 112)">
|
||||
<rect id="矩形" stroke="#EBEBEB" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="12"></rect>
|
||||
<g id="使用帮助" transform="translate(8, 8)" stroke="#171719" stroke-width="1.7">
|
||||
<g id="编组-16" transform="translate(2.7, 1.2)">
|
||||
<rect id="矩形" x="0" y="3.20000312" width="18.6000182" height="16.2000158" rx="3.5"></rect>
|
||||
<path d="M6.43068654,1.74093758 L9.44000922,3.20000313 L9.44000922,3.20000313 L9.44000922,19.2000188 L4.24931901,16.6833205 C3.38736738,16.2654045 2.84000277,15.3917071 2.84000277,14.4337852 L2.84000277,3.99047285 C2.84000277,2.60976098 3.9592909,1.49047285 5.34000277,1.49047285 C5.71781927,1.49047285 6.09072193,1.57610626 6.43068654,1.74093758 Z" id="矩形" fill="#FFFFFF" stroke-linejoin="round"></path>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 336 KiB |
@@ -1,26 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 25</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-1152, -556)">
|
||||
<g id="快捷操作" transform="translate(1120, 502)">
|
||||
<g id="编组-11" transform="translate(2, 38)">
|
||||
<g id="编组-25" transform="translate(30, 16)">
|
||||
<rect id="矩形" stroke="#DFE4ED" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="8"></rect>
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1136, -600)">
|
||||
<g id="快捷操作" transform="translate(1100, 554)">
|
||||
<g id="编组-11" transform="translate(2, 34)">
|
||||
<g id="编组-25" transform="translate(34, 12)">
|
||||
<rect id="矩形" stroke="#EBEBEB" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="12"></rect>
|
||||
<g id="模型管理" transform="translate(9, 9)">
|
||||
<rect id="矩形" stroke="#212332" stroke-width="1.7" stroke-linejoin="round" x="2.2" y="7.7" width="12.1" height="12.1"></rect>
|
||||
<polygon id="矩形" stroke="#212332" stroke-width="1.7" stroke-linejoin="round" points="7.7 2.2 19.8 2.2 14.3 7.7 2.2 7.7"></polygon>
|
||||
<polygon id="矩形" stroke="#212332" stroke-width="1.7" stroke-linejoin="round" points="7.7 14.3 19.8 14.3 14.3 19.8 2.2 19.8"></polygon>
|
||||
<polygon id="矩形备份-9" stroke="#212332" stroke-width="1.7" stroke-linejoin="round" points="14.3 7.7 19.8 2.2 19.8 14.3 14.3 19.8"></polygon>
|
||||
<line x1="7.7" y1="2.2" x2="7.7" y2="14.3" id="路径-10" stroke="#212332" stroke-width="1.7"></line>
|
||||
<path d="M7.7,4.4 C8.91502645,4.4 9.9,3.41502645 9.9,2.2 C9.9,0.98497355 8.91502645,0 7.7,0 C6.48497355,0 5.5,0.98497355 5.5,2.2 C5.5,3.41502645 6.48497355,4.4 7.7,4.4 Z" id="椭圆形" fill="#212332"></path>
|
||||
<path d="M8.25899503,16.5 C9.47402148,16.5 10.458995,15.5150264 10.458995,14.3 C10.458995,13.0849736 9.47402148,12.1 8.25899503,12.1 C7.04396858,12.1 6.05899503,13.0849736 6.05899503,14.3 C6.05899503,15.5150264 7.04396858,16.5 8.25899503,16.5 Z" id="椭圆形" fill="#212332"></path>
|
||||
<path d="M2.2,9.9 C3.41502645,9.9 4.4,8.91502645 4.4,7.7 C4.4,6.48497355 3.41502645,5.5 2.2,5.5 C0.98497355,5.5 0,6.48497355 0,7.7 C0,8.91502645 0.98497355,9.9 2.2,9.9 Z" id="椭圆形备份-2" fill="#212332"></path>
|
||||
<path d="M2.2,22 C3.41502645,22 4.4,21.0150264 4.4,19.8 C4.4,18.5849736 3.41502645,17.6 2.2,17.6 C0.98497355,17.6 0,18.5849736 0,19.8 C0,21.0150264 0.98497355,22 2.2,22 Z" id="椭圆形备份-3" fill="#212332"></path>
|
||||
<path d="M19.8,4.4 C21.0150264,4.4 22,3.41502645 22,2.2 C22,0.98497355 21.0150264,0 19.8,0 C18.5849736,0 17.6,0.98497355 17.6,2.2 C17.6,3.41502645 18.5849736,4.4 19.8,4.4 Z" id="椭圆形" fill="#212332"></path>
|
||||
<path d="M19.8,16.5 C21.0150264,16.5 22,15.5150264 22,14.3 C22,13.0849736 21.0150264,12.1 19.8,12.1 C18.5849736,12.1 17.6,13.0849736 17.6,14.3 C17.6,15.5150264 18.5849736,16.5 19.8,16.5 Z" id="椭圆形" fill="#212332"></path>
|
||||
<path d="M14.3,9.9 C15.5150264,9.9 16.5,8.91502645 16.5,7.7 C16.5,6.48497355 15.5150264,5.5 14.3,5.5 C13.0849736,5.5 12.1,6.48497355 12.1,7.7 C12.1,8.91502645 13.0849736,9.9 14.3,9.9 Z" id="椭圆形备份-2" fill="#212332"></path>
|
||||
<path d="M14.3,22 C15.5150264,22 16.5,21.0150264 16.5,19.8 C16.5,18.5849736 15.5150264,17.6 14.3,17.6 C13.0849736,17.6 12.1,18.5849736 12.1,19.8 C12.1,21.0150264 13.0849736,22 14.3,22 Z" id="椭圆形备份-3" fill="#212332"></path>
|
||||
<rect id="矩形" stroke="#171719" stroke-width="1.7" stroke-linejoin="round" x="2.2" y="7.7" width="12.1" height="12.1"></rect>
|
||||
<polygon id="矩形" stroke="#171719" stroke-width="1.7" stroke-linejoin="round" points="7.7 2.2 19.8 2.2 14.3 7.7 2.2 7.7"></polygon>
|
||||
<polygon id="矩形" stroke="#171719" stroke-width="1.7" stroke-linejoin="round" points="7.7 14.3 19.8 14.3 14.3 19.8 2.2 19.8"></polygon>
|
||||
<polygon id="矩形备份-9" stroke="#171719" stroke-width="1.7" stroke-linejoin="round" points="14.3 7.7 19.8 2.2 19.8 14.3 14.3 19.8"></polygon>
|
||||
<line x1="7.7" y1="2.2" x2="7.7" y2="14.3" id="路径-10" stroke="#171719" stroke-width="1.7"></line>
|
||||
<path d="M7.7,4.4 C8.91502645,4.4 9.9,3.41502645 9.9,2.2 C9.9,0.98497355 8.91502645,0 7.7,0 C6.48497355,0 5.5,0.98497355 5.5,2.2 C5.5,3.41502645 6.48497355,4.4 7.7,4.4 Z" id="椭圆形" fill="#171719"></path>
|
||||
<path d="M8.25899503,16.5 C9.47402148,16.5 10.458995,15.5150264 10.458995,14.3 C10.458995,13.0849736 9.47402148,12.1 8.25899503,12.1 C7.04396858,12.1 6.05899503,13.0849736 6.05899503,14.3 C6.05899503,15.5150264 7.04396858,16.5 8.25899503,16.5 Z" id="椭圆形" fill="#171719"></path>
|
||||
<path d="M2.2,9.9 C3.41502645,9.9 4.4,8.91502645 4.4,7.7 C4.4,6.48497355 3.41502645,5.5 2.2,5.5 C0.98497355,5.5 0,6.48497355 0,7.7 C0,8.91502645 0.98497355,9.9 2.2,9.9 Z" id="椭圆形备份-2" fill="#171719"></path>
|
||||
<path d="M2.2,22 C3.41502645,22 4.4,21.0150264 4.4,19.8 C4.4,18.5849736 3.41502645,17.6 2.2,17.6 C0.98497355,17.6 0,18.5849736 0,19.8 C0,21.0150264 0.98497355,22 2.2,22 Z" id="椭圆形备份-3" fill="#171719"></path>
|
||||
<path d="M19.8,4.4 C21.0150264,4.4 22,3.41502645 22,2.2 C22,0.98497355 21.0150264,0 19.8,0 C18.5849736,0 17.6,0.98497355 17.6,2.2 C17.6,3.41502645 18.5849736,4.4 19.8,4.4 Z" id="椭圆形" fill="#171719"></path>
|
||||
<path d="M19.8,16.5 C21.0150264,16.5 22,15.5150264 22,14.3 C22,13.0849736 21.0150264,12.1 19.8,12.1 C18.5849736,12.1 17.6,13.0849736 17.6,14.3 C17.6,15.5150264 18.5849736,16.5 19.8,16.5 Z" id="椭圆形" fill="#171719"></path>
|
||||
<path d="M14.3,9.9 C15.5150264,9.9 16.5,8.91502645 16.5,7.7 C16.5,6.48497355 15.5150264,5.5 14.3,5.5 C13.0849736,5.5 12.1,6.48497355 12.1,7.7 C12.1,8.91502645 13.0849736,9.9 14.3,9.9 Z" id="椭圆形备份-2" fill="#171719"></path>
|
||||
<path d="M14.3,22 C15.5150264,22 16.5,21.0150264 16.5,19.8 C16.5,18.5849736 15.5150264,17.6 14.3,17.6 C13.0849736,17.6 12.1,18.5849736 12.1,19.8 C12.1,21.0150264 13.0849736,22 14.3,22 Z" id="椭圆形备份-3" fill="#171719"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 14</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-408, -232)">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-404, -196)">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-14" transform="translate(152, 16)">
|
||||
<rect id="矩形" fill="#155EEF" x="0" y="0" width="32" height="32" rx="8"></rect>
|
||||
<g id="模型管理" transform="translate(8, 7)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 26</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-1254, -556)">
|
||||
<g id="快捷操作" transform="translate(1120, 502)">
|
||||
<g id="编组-11" transform="translate(2, 38)">
|
||||
<g id="编组-26" transform="translate(132, 16)">
|
||||
<rect id="矩形" stroke="#DFE4ED" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="8"></rect>
|
||||
<g id="空间管理" transform="translate(11, 10)" stroke="#212332" stroke-linejoin="round" stroke-width="1.7">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1244, -600)">
|
||||
<g id="快捷操作" transform="translate(1100, 554)">
|
||||
<g id="编组-11" transform="translate(2, 34)">
|
||||
<g id="编组-26" transform="translate(142, 12)">
|
||||
<rect id="矩形" stroke="#EBEBEB" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="12"></rect>
|
||||
<g id="空间管理" transform="translate(11, 10)" stroke="#171719" stroke-linejoin="round" stroke-width="1.7">
|
||||
<polygon id="路径" points="0 5 9 0 18 5 18 15 9 20 0 15"></polygon>
|
||||
<polygon id="路径-8" points="9.00139153 1 1 15 17 15"></polygon>
|
||||
<polygon id="路径-8" transform="translate(9, 11) scale(1, -1) translate(-9, -11)" points="9.00065228 8 5.25 14 12.75 14"></polygon>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -7,10 +7,10 @@
|
||||
<stop stop-color="#369F21" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-624, -232)">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份" transform="translate(216, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-616, -196)">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份" transform="translate(212, 0)">
|
||||
<g id="编组-32" transform="translate(152, 16)">
|
||||
<rect id="矩形" fill="url(#linearGradient-1)" x="0" y="0" width="32" height="32" rx="8"></rect>
|
||||
<g id="空间-(1)" transform="translate(5, 7)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
|
||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
@@ -1,13 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 24</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-1152, -664)">
|
||||
<g id="快捷操作" transform="translate(1120, 502)">
|
||||
<g id="编组-11" transform="translate(2, 38)">
|
||||
<g id="编组-24" transform="translate(30, 124)">
|
||||
<rect id="矩形" stroke="#DFE4ED" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="8"></rect>
|
||||
<g id="用户管理" transform="translate(9, 10)" stroke="#212332" stroke-width="1.7">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-1352, -600)">
|
||||
<g id="快捷操作" transform="translate(1100, 554)">
|
||||
<g id="编组-11" transform="translate(2, 34)">
|
||||
<g id="编组-24" transform="translate(250, 12)">
|
||||
<rect id="矩形" stroke="#EBEBEB" fill="#FFFFFF" x="0.5" y="0.5" width="39" height="39" rx="12"></rect>
|
||||
<g id="用户管理" transform="translate(9, 10)" stroke="#171719" stroke-width="1.7">
|
||||
<path d="M10.4752216,9.8347602 C12.0826337,9.8347602 13.5411901,10.4629457 14.5971671,11.4807313 C15.6415902,12.4873807 16.2916977,13.8763311 16.2916977,15.41013 L16.2916987,17.2469287 C16.292495,17.768016 16.0699552,18.2385306 15.715897,18.5800613 C15.3496472,18.9333521 14.8431448,19.15 14.2862956,19.15 L2.85772417,19.15 C2.30025073,19.15 1.79383233,18.9336648 1.42754075,18.5806207 C1.07279965,18.2387094 0.85,17.7674542 0.85,17.2456298 L0.85,15.41013 C0.85,13.8763309 1.50010692,12.4873802 2.54452956,11.4807307 C3.60050579,10.4629455 5.05906118,9.8347602 6.6664729,9.8347602 Z" id="路径" fill-rule="nonzero"></path>
|
||||
<path d="M8.51095419,0.85 C9.31202689,0.85 10.0374296,1.17449643 10.5625442,1.69913767 C11.0873448,2.22346508 11.4120985,2.94775006 11.4120985,3.74776253 C11.4120985,4.54882217 11.0868662,5.27413048 10.5618329,5.79931671 C10.0365783,6.32472447 9.31123944,6.65 8.51095419,6.65 C7.71104886,6.65 6.98645948,6.32499629 6.46178132,5.80015797 C5.93701763,5.27523409 5.61209853,4.5502916 5.61209853,3.75 C5.61209853,2.94883778 5.93633819,2.22374798 6.46093583,1.69899021 C6.98544108,1.17432485 7.71016945,0.85 8.51095419,0.85 Z" id="路径" fill-rule="nonzero"></path>
|
||||
<path d="M16.5,8 C17.8807119,8 19,6.88071187 19,5.5 C19,4.11928813 17.8807119,3 16.5,3" id="路径" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 33</title>
|
||||
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="首页" transform="translate(-840, -232)">
|
||||
<g id="核心数据" transform="translate(256, 216)">
|
||||
<g id="编组-19备份-2" transform="translate(432, 0)">
|
||||
<g id="空间外层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="工作台-首页" transform="translate(-828, -196)">
|
||||
<g id="核心数据" transform="translate(252, 180)">
|
||||
<g id="编组-19备份-2" transform="translate(424, 0)">
|
||||
<g id="编组-33" transform="translate(152, 16)">
|
||||
<rect id="矩形" fill="#4DA8FF" x="0" y="0" width="32" height="32" rx="8"></rect>
|
||||
<g id="用户总数总计" transform="translate(6, 8)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
web/src/assets/images/tool/market.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -127,23 +127,23 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
className={clsx(
|
||||
"rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')]",
|
||||
file.type.includes('pdf')
|
||||
? "rb:bg-[url('src/assets/images/file/pdf.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/pdf.svg')]"
|
||||
: (file.type.includes('excel') || file.type.includes('spreadsheetml.sheet'))
|
||||
? "rb:bg-[url('src/assets/images/file/excel.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/excel.svg')]"
|
||||
: file.type.includes('csv')
|
||||
? "rb:bg-[url('src/assets/images/file/csv.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/csv.svg')]"
|
||||
: file.type.includes('html')
|
||||
? "rb:bg-[url('src/assets/images/file/html.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/html.svg')]"
|
||||
: file.type.includes('json')
|
||||
? "rb:bg-[url('src/assets/images/file/json.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/json.svg')]"
|
||||
: file.type.includes('ppt')
|
||||
? "rb:bg-[url('src/assets/images/file/ppt.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/ppt.svg')]"
|
||||
: file.type.includes('text')
|
||||
? "rb:bg-[url('src/assets/images/file/txt.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/txt.svg')]"
|
||||
: file.type.includes('markdown')
|
||||
? "rb:bg-[url('src/assets/images/file/md.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/md.svg')]"
|
||||
: (file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document'))
|
||||
? "rb:bg-[url('src/assets/images/file/word.svg')]"
|
||||
? "rb:bg-[url('@/assets/images/file/word.svg')]"
|
||||
: null
|
||||
)}
|
||||
></div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-02 15:21:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-24 14:59:53
|
||||
* @Last Modified time: 2026-03-20 20:24:43
|
||||
*/
|
||||
/**
|
||||
* RbCard Component
|
||||
@@ -23,6 +23,7 @@ import clsx from 'clsx';
|
||||
|
||||
/** Props interface for RbCard component */
|
||||
interface RbCardProps extends CardProps {
|
||||
isNeedTooltip?: boolean;
|
||||
children?: ReactNode;
|
||||
/** Custom avatar component */
|
||||
avatarText?: string;
|
||||
@@ -32,16 +33,22 @@ interface RbCardProps extends CardProps {
|
||||
/** Click handler */
|
||||
onClick?: () => void;
|
||||
footer?: ReactNode;
|
||||
headerClassName?: string;
|
||||
titleClassName?: string;
|
||||
}
|
||||
|
||||
/** Custom card component with flexible styling and header options */
|
||||
const RbCard: FC<RbCardProps> = ({
|
||||
isNeedTooltip = true,
|
||||
title,
|
||||
children,
|
||||
avatarText,
|
||||
avatarClassName,
|
||||
avatarUrl,
|
||||
footer,
|
||||
headerClassName,
|
||||
titleClassName,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
@@ -54,17 +61,23 @@ const RbCard: FC<RbCardProps> = ({
|
||||
: avatarText
|
||||
? <Flex align="center" justify="center" className={clsx(avatarClassName, "rb:size-11 rb:rounded-lg rb:text-[24px] rb:text-[#ffffff] rb:bg-[#155EEF]")}>{avatarText}</Flex> : null
|
||||
}
|
||||
<Tooltip title={title}>
|
||||
<div className="rb:flex-1 rb:leading-5.5 rb:min-w-0 rb:whitespace-break-spaces rb:wrap-break-word rb:line-clamp-2">
|
||||
{isNeedTooltip
|
||||
? <Tooltip title={title}>
|
||||
<div className={`rb:flex-1 rb:leading-5.5 rb:min-w-0 rb:whitespace-break-spaces rb:wrap-break-word rb:line-clamp-2 ${titleClassName}`}>
|
||||
{title}
|
||||
</div>
|
||||
</Tooltip>
|
||||
: <div className="rb:flex-1 rb:leading-5.5 rb:min-w-0 rb:whitespace-break-spaces rb:wrap-break-word rb:line-clamp-2">
|
||||
{title}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
</Flex>}
|
||||
classNames={{
|
||||
header: 'rb:text-[16px] rb:p-[16px_16px_8px_16px]! rb:border-0!',
|
||||
body: 'rb:p-4! rb:bg-white!',
|
||||
header: `rb:text-[16px] rb:p-[16px_16px_8px_16px]! rb:border-0! ${headerClassName}`,
|
||||
body: 'rb:p-4! rb:pt-2! rb:bg-white!',
|
||||
}}
|
||||
className="rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]! rb:group"
|
||||
className={`rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]! rb:group ${className}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-02 15:24:23
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-10 14:59:39
|
||||
* @Last Modified time: 2026-03-23 11:35:33
|
||||
*/
|
||||
/**
|
||||
* SearchInput Component
|
||||
@@ -22,7 +22,7 @@ import { Input, type InputProps } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
/** Props interface for SearchInput component */
|
||||
interface SearchInputProps {
|
||||
interface SearchInputProps extends InputProps {
|
||||
/** Placeholder text */
|
||||
placeholder?: string;
|
||||
/** Callback fired when search value changes */
|
||||
|
||||
@@ -467,6 +467,7 @@ export const en = {
|
||||
notAllSpaces: 'Cannot be all spaces',
|
||||
download: 'Download',
|
||||
view: 'View',
|
||||
updated_at: 'Updated At',
|
||||
},
|
||||
model: {
|
||||
searchPlaceholder: 'search model…',
|
||||
@@ -1918,6 +1919,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
refreshFailed: 'Refresh Failed',
|
||||
|
||||
// Market related
|
||||
mcpMarket: 'MCP Market',
|
||||
availableMcp: 'Available MCP Services',
|
||||
descEmpty: 'There is currently no introduction available …',
|
||||
marketSelectTitle: 'Select an MCP Market',
|
||||
marketSelectDesc: 'Choose a market source from the left, configure the connection to browse MCP services',
|
||||
marketRefreshSuccess: 'List refreshed',
|
||||
@@ -1966,6 +1970,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
timeout: 'Timeout (seconds)',
|
||||
sseReadTimeout: 'SSE Read Timeout (seconds)',
|
||||
saveAndTest: 'Save and Test',
|
||||
testLink: 'Connection Test',
|
||||
noTags: 'There are no tags here…',
|
||||
|
||||
timeFormat: 'Time Formatting',
|
||||
timeZoneConversion: 'Time Zone Conversion',
|
||||
|
||||
@@ -1104,6 +1104,7 @@ export const zh = {
|
||||
notAllSpaces: '不能是纯空格',
|
||||
download: '下载',
|
||||
view: '查看',
|
||||
updated_at: '更新时间',
|
||||
},
|
||||
model: {
|
||||
searchPlaceholder: '搜索模型…',
|
||||
@@ -1914,6 +1915,9 @@ export const zh = {
|
||||
refreshFailed: '刷新失败',
|
||||
|
||||
// Market 相关
|
||||
mcpMarket: 'MCP Market',
|
||||
availableMcp: '可用的 MCP 服务',
|
||||
descEmpty: '暂无介绍…',
|
||||
marketSelectTitle: '选择一个 MCP 市场',
|
||||
marketSelectDesc: '从左侧选择一个市场源,配置连接后即可浏览该市场的 MCP 服务',
|
||||
marketRefreshSuccess: '列表已刷新',
|
||||
@@ -1962,6 +1966,8 @@ export const zh = {
|
||||
timeout: '超时时间(秒)',
|
||||
sseReadTimeout: 'SSE 读取超时时间(秒)',
|
||||
saveAndTest: '保存并测试',
|
||||
testLink: '连接测试',
|
||||
noTags: '暂无标签…',
|
||||
|
||||
timeFormat: '时间格式化',
|
||||
timeZoneConversion: '时区转换',
|
||||
|
||||
@@ -299,7 +299,7 @@ const Conversation: FC = () => {
|
||||
<Flex className="rb:w-full rb:p-[-16px]!">
|
||||
<div className="rb:w-80 rb:h-screen rb:bg-[#F6F6F6] rb:overflow-hidden">
|
||||
<Flex align="center" gap={8} className="rb:p-5!">
|
||||
<div className="rb:size-6 rb:bg-cover rb:bg-[url('src/assets/images/conversation/redbear.png')]"></div>
|
||||
<div className="rb:size-6 rb:bg-cover rb:bg-[url('@/assets/images/conversation/redbear.png')]"></div>
|
||||
<div className="rb:text-[16px] rb:leading-5 rb:font-[Gilroy-Extrabold] rb:font-extrabold">{t('memoryConversation.chatTitle')}</div>
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import guideBgImg from '@/assets/images/index/guide_bg@2x.png'
|
||||
import { Button, Tour } from 'antd';
|
||||
import type { TourProps } from 'antd';
|
||||
import arrowRight from '@/assets/images/index/arrow_right_blue.svg'
|
||||
|
||||
const GuideCard: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -67,22 +65,18 @@ const GuideCard: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='rb:w-full rb:p-4' style={{ backgroundImage: `url(${guideBgImg})`, backgroundSize: '100% 100%' }}>
|
||||
<div className='rb:flex rb:justify-start rb:text-white rb:text-base rb:font-semibold' >
|
||||
{ t('index.getStarted')}
|
||||
<div className='rb:w-full rb:bg-white rb:rounded-xl rb:pb-3'>
|
||||
<div className='rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:p-3 rb:bg-cover rb:rounded-tl-xl rb:rounded-tr-xl rb:bg-[url("@/assets/images/index/guide_bg@2x.png")]' >
|
||||
{ t('index.getStarted')}
|
||||
</div>
|
||||
<div className='rb:flex rb:text-xs rb:text-white rb:leading-[18px] rb:mt-3'>
|
||||
<div className='rb:leading-4.5 rb:text-[12px] rb:-mt-2 rb:pl-3 rb:pr-1.75'>
|
||||
{ t('index.startedDesc')}
|
||||
</div>
|
||||
<div className='rb:flex rb:w-full rb:items-center rb:justify-between rb:gap-3 rb:mt-4'>
|
||||
<Button ref={startButtonRef} className='rb:gap-2 rb:w-full rb:flex rb:items-center rb:text-[#155EEF]' onClick={handleStartGuide}>
|
||||
<span className='rb:text-xs'>{ t('index.viewGuide')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
{/* <Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#155EEF]'>
|
||||
<span className='rb:text-xs'>{ t('index.watchVideo')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button> */}
|
||||
<div className="rb:mt-2 rb:pl-3 rb:pr-4">
|
||||
<Button ref={startButtonRef} block className='rb:gap-1 rb:flex rb:items-center' onClick={handleStartGuide}>
|
||||
<span className='rb:text-xs'>{t('index.viewGuide')}</span>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/arrow_right_dark.svg')]"></div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flex } from 'antd'
|
||||
|
||||
import modelIcon from '@/assets/images/index/model_mgt.svg'
|
||||
import spaceIcon from '@/assets/images/index/space_mgt.svg'
|
||||
@@ -89,25 +90,30 @@ const QuickActions: FC<QuickActionsProps> = ({ onNavigate }) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='rb:w-full rb:p-4 rb:bg-[#FBFDFF] rb:border-1 rb:border-[#DFE4ED] rb:rounded-xl'>
|
||||
<div className='rb:flex rb:justify-start rb:text-base rb:font-medium rb:text-[#212332]'>
|
||||
{ t('quickActions.title') }
|
||||
</div>
|
||||
<div className="rb:grid rb:grid-cols-3 md:rb:grid-cols-4 rb:gap-4 rb:mt-4">
|
||||
|
||||
{quickActions.map((action) => (
|
||||
<div key={action.key}
|
||||
className="rb:flex rb:flex-col rb:items-center rb:text-center rb:cursor-pointer rb:group"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<img src={action.icon} className='rb:size-10 rb:mx-auto' />
|
||||
<div className="rb:mt-2 rb:text-xs rb:max-w-[74px] rb:text-[#5B6167] rb:text-center rb:leading-[14px]">
|
||||
{action.title}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>);
|
||||
<div className='rb:w-full rb:bg-white rb:rounded-xl rb:mt-2.5 rb:py-3'>
|
||||
<div className='rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:px-4 rb:pb-3.5'>
|
||||
{ t('quickActions.title') }
|
||||
</div>
|
||||
<div className="rb:grid rb:grid-cols-3 md:rb:grid-cols-4 rb:gap-4">
|
||||
{quickActions.map((action) => (
|
||||
<Flex
|
||||
key={action.key}
|
||||
vertical
|
||||
gap={8}
|
||||
align="center"
|
||||
justify="center"
|
||||
className="rb:cursor-pointer"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<img src={action.icon} className='rb:size-10 rb:mx-auto' />
|
||||
<div className="rb:text-[12px] rb:max-w-18.25 rb:text-[#5B6167] rb:text-center rb:leading-3.5">
|
||||
{action.title}
|
||||
</div>
|
||||
</Flex>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickActions;
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import totalModels from '@/assets/images/index/models.svg';
|
||||
import totalSpaces from '@/assets/images/index/spaces.svg';
|
||||
import totalUsers from '@/assets/images/index/users.svg';
|
||||
import totalApps from '@/assets/images/index/apps.svg';
|
||||
import arrowUpDb from '@/assets/images/index/arrow_up_d.svg'
|
||||
import arrowDownDb from '@/assets/images/index/arrow_down_d.svg'
|
||||
import arrowUp from '@/assets/images/index/arrow_up.svg'
|
||||
import arrowDown from '@/assets/images/index/arrow_down.svg'
|
||||
import styles from './index.module.css'
|
||||
import { Flex } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { type DataResponse } from '@/api/common'
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
const list = [
|
||||
{
|
||||
key: 'models',
|
||||
icon: totalModels,
|
||||
icon: 'rb:bg-[url("@/assets/images/index/models.svg")]',
|
||||
value: '24',
|
||||
// trendValue: '12.5%',
|
||||
trend: 'up',
|
||||
@@ -25,7 +20,7 @@ const list = [
|
||||
},
|
||||
{
|
||||
key: 'spaces',
|
||||
icon: totalSpaces,
|
||||
icon: 'rb:bg-[url("@/assets/images/index/spaces.svg")]',
|
||||
value: '156',
|
||||
trendValue: '+8',
|
||||
trend: 'down',
|
||||
@@ -36,7 +31,7 @@ const list = [
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
icon: totalUsers,
|
||||
icon: 'rb:bg-[url("@/assets/images/index/users.svg")]',
|
||||
value: '1,248',
|
||||
trendValue: '+42',
|
||||
trend: 'up',
|
||||
@@ -47,7 +42,7 @@ const list = [
|
||||
},
|
||||
{
|
||||
key: 'running_apps',
|
||||
icon: totalApps,
|
||||
icon: 'rb:bg-[url("@/assets/images/index/apps.svg")]',
|
||||
value: '12.8k',
|
||||
trendValue: '98.7%',
|
||||
trend: 'up',
|
||||
@@ -60,70 +55,106 @@ const list = [
|
||||
const TopCardList: FC<{data?: DataResponse}> = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-3 rb:mt-3">
|
||||
{list.map((item) => {
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
className={styles.card}
|
||||
style={{
|
||||
background: item.background,
|
||||
}}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className="rb:text-xs rb:font-medium rb:text-[#212332] rb:w-[96px]">{t(`dashboard.${'total_' + item.key}`)}</div>
|
||||
<div className={styles.avatar}><img src={item.icon} /></div>
|
||||
</div>
|
||||
<div
|
||||
key={item.key}
|
||||
className="rb:bg-white rb:rounded-xl rb:p-4"
|
||||
>
|
||||
<Flex justify="space-between" align="center" gap={24}>
|
||||
<div className="rb:text-[12pxx] rb:font-medium rb:flex-1">{t(`dashboard.${'total_' + item.key}`)}</div>
|
||||
<div className={`rb:size-8 rb:bg-cover ${item.icon}`}></div>
|
||||
</Flex>
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className="rb:mt-5 rb:font-[MiSans-Bold] rb:font-bold rb:text-[24px] rb:leading-8">
|
||||
{item.key === 'spaces' && String(data?.active_workspaces)}
|
||||
{item.key === 'running_apps' && String(data?.[`${item.key}` as keyof DataResponse] || item.value || 0)}
|
||||
{item.key !== 'spaces' && item.key !== 'running_apps' && String(data?.[`total_${item.key}` as keyof DataResponse] || item.value || 0)}
|
||||
</div>
|
||||
<div className='rb:flex rb:flex-col rb:items-start'>
|
||||
{item.key === 'models' ? (
|
||||
<div className='rb:text-xs rb:leading-4 rb:text-[#5F6266] rb:w-[130px]'>
|
||||
{t(`dashboard.${'desc_' + item.key}`, { account: data?.total_llm, nums: data?.total_embedding })}
|
||||
<div className='rb:flex rb:flex-col rb:items-start rb:mt-2'>
|
||||
{item.key === 'models'
|
||||
? (
|
||||
<div className='rb:text-xs rb:leading-4 rb:text-[#5F6266] rb:w-32.25'>
|
||||
{t(`dashboard.${'desc_' + item.key}`, { account: data?.total_llm, nums: data?.total_embedding })}
|
||||
</div>
|
||||
) : (<>
|
||||
)
|
||||
: (<>
|
||||
<div className='rb:flex rb:items-center rb:text-xs rb:leading-4 rb:gap-1'>
|
||||
{item.key === 'spaces' && (<>
|
||||
<img src={Number(data?.new_workspaces_this_week || 0) >= 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/>
|
||||
<span className={Number(data?.new_workspaces_this_week || 0) >= 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_workspaces_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_workspaces_this_week || 0))}</span>
|
||||
<div className={clsx("rb:size-3 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up_d.svg')]": Number(data?.new_workspaces_this_week || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_workspaces_this_week || 0) < 0,
|
||||
})}></div>
|
||||
<span className={clsx('rb:font-medium', {
|
||||
"rb:text-[#369F21]": Number(data?.new_workspaces_this_week || 0) >= 0,
|
||||
"rb:text-[#FF5D34]": Number(data?.new_workspaces_this_week || 0) < 0,
|
||||
})}>{Number(data?.new_workspaces_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_workspaces_this_week || 0))}</span>
|
||||
</>)}
|
||||
{item.key === 'users' && (<>
|
||||
<img src={Number(data?.new_users_this_week || 0) >= 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/>
|
||||
<span className={Number(data?.new_users_this_week || 0) >= 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_users_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_users_this_week || 0))}</span>
|
||||
<div className={clsx("rb:size-3 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up_d.svg')]": Number(data?.new_users_this_week || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_users_this_week || 0) < 0,
|
||||
})}></div>
|
||||
<span className={clsx('rb:font-medium', {
|
||||
"rb:text-[#369F21]": Number(data?.new_users_this_week || 0) >= 0,
|
||||
"rb:text-[#FF5D34]": Number(data?.new_users_this_week || 0) < 0,
|
||||
})}>{Number(data?.new_users_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_users_this_week || 0))}</span>
|
||||
</>)}
|
||||
{item.key === 'running_apps' && (<>
|
||||
<img src={Number(data?.new_apps_this_week || 0) >= 0 ? arrowUpDb : arrowDownDb} className='rb:size-3'/>
|
||||
<span className={Number(data?.new_apps_this_week || 0) >= 0 ? 'rb:text-[#369F21]' : 'rb:text-[#FF5D34]'}>{Number(data?.new_apps_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_apps_this_week || 0))}</span>
|
||||
<div className={clsx("rb:size-3 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up_d.svg')]": Number(data?.new_apps_this_week || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down_d.svg')]": Number(data?.new_apps_this_week || 0) < 0,
|
||||
})}></div>
|
||||
<span className={clsx('rb:font-medium', {
|
||||
"rb:text-[#369F21]": Number(data?.new_apps_this_week || 0) >= 0,
|
||||
"rb:text-[#FF5D34]": Number(data?.new_apps_this_week || 0) < 0,
|
||||
})}>{Number(data?.new_apps_this_week || 0) >= 0 ? '+' : '-'}{Math.abs(Number(data?.new_apps_this_week || 0))}</span>
|
||||
</>)}
|
||||
|
||||
</div>
|
||||
<div className='rb:text-xs rb:leading-4 rb:text-[#5F6266]'>
|
||||
<div className='rb:text-[12px] rb:leading-4 rb:text-[#5F6266]'>
|
||||
{t(`dashboard.${'desc_' + item.key}`)}
|
||||
</div>
|
||||
</>)}
|
||||
</>)
|
||||
}
|
||||
</div>
|
||||
|
||||
{item.key === 'models' && (<div className={`rb:flex rb:max-w-40 rb:text-xs rb:mt-2 rb:items-center rb:gap-1 rb:border-1 rb:rounded rb:px-2 rb:py-0.5 ${Number(data?.model_week_growth_rate || 0) >= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}>
|
||||
<img src={Number(data?.model_week_growth_rate || 0) >= 0 ? arrowUp : arrowDown} className='rb:size-3'/>
|
||||
<span>{Math.abs(Number(data?.model_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</div>)}
|
||||
{item.key === 'spaces' && (<div className={`rb:flex rb:max-w-40 rb:text-xs rb:mt-2 rb:items-center rb:gap-1 rb:border-1 rb:rounded rb:px-2 rb:py-0.5 ${Number(data?.workspace_week_growth_rate || 0) >= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}>
|
||||
<img src={Number(data?.workspace_week_growth_rate || 0) >= 0 ? arrowUp : arrowDown} className='rb:size-3'/>
|
||||
<span>{Math.abs(Number(data?.workspace_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</div>)}
|
||||
{item.key === 'users' && (<div className={`rb:flex rb:max-w-40 rb:text-xs rb:mt-2 rb:items-center rb:gap-1 rb:border-1 rb:rounded rb:px-2 rb:py-0.5 ${Number(data?.user_week_growth_rate || 0) >= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}>
|
||||
<img src={Number(data?.user_week_growth_rate || 0) >= 0 ? arrowUp : arrowDown} className='rb:size-3'/>
|
||||
<span>{Math.abs(Number(data?.user_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</div>)}
|
||||
{item.key === 'running_apps' && (<div className={`rb:flex rb:max-w-40 rb:text-xs rb:mt-2 rb:items-center rb:gap-1 rb:border-1 rb:rounded rb:px-2 rb:py-0.5 ${Number(data?.app_week_growth_rate || 0) >= 0 ? 'rb:text-[#369F21] rb:border-[#369F21] rb:bg-[rgba(54, 159, 33, 0.25)]' : 'rb:text-[#FF5D34] rb:border-[#FF5D34] rb:bg-[rgba(255, 93, 52, 0.25)]'}`}>
|
||||
<img src={Number(data?.app_week_growth_rate || 0) >= 0 ? arrowUp : arrowDown} className='rb:size-3'/>
|
||||
<span>{Math.abs(Number(data?.app_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</div>)}
|
||||
|
||||
{item.key === 'models' && (<Tag color={Number(data?.model_week_growth_rate || 0) >= 0 ? "success" : "warning"} className="rb:mt-2">
|
||||
<Flex align="center">
|
||||
<div className={clsx("rb:size-3.5 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up.svg')]": Number(data?.model_week_growth_rate || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.model_week_growth_rate || 0) < 0,
|
||||
})}></div>
|
||||
<span>{Math.abs(Number(data?.model_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</Flex>
|
||||
</Tag>)}
|
||||
{item.key === 'spaces' && (<Tag color={Number(data?.workspace_week_growth_rate || 0) >= 0 ? "success" : "warning"} className="rb:mt-2">
|
||||
<Flex align="center">
|
||||
<div className={clsx("rb:size-3.5 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up.svg')]": Number(data?.workspace_week_growth_rate || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.workspace_week_growth_rate || 0) < 0,
|
||||
})}></div>
|
||||
<span>{Math.abs(Number(data?.workspace_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</Flex>
|
||||
</Tag>)}
|
||||
{item.key === 'users' && (<Tag color={Number(data?.user_week_growth_rate || 0) >= 0 ? "success" : "warning"} className="rb:mt-2">
|
||||
<Flex align="center">
|
||||
<div className={clsx("rb:size-3.5 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up.svg')]": Number(data?.user_week_growth_rate || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.user_week_growth_rate || 0) < 0,
|
||||
})}></div>
|
||||
<span>{Math.abs(Number(data?.user_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</Flex>
|
||||
</Tag>)}
|
||||
{item.key === 'running_apps' && (<Tag color={Number(data?.app_week_growth_rate || 0) >= 0 ? "success" : "warning"} className="rb:mt-2">
|
||||
<Flex align="center">
|
||||
<div className={clsx("rb:size-3.5 rb:bg-cover rb:mr-0.5", {
|
||||
"rb:bg-[url('@/assets/images/index/arrow_up.svg')]": Number(data?.app_week_growth_rate || 0) >= 0,
|
||||
"rb:bg-[url('@/assets/images/index/arrow_down.svg')]": Number(data?.app_week_growth_rate || 0) < 0,
|
||||
})}></div>
|
||||
<span>{Math.abs(Number(data?.app_week_growth_rate || 0))}% {t('dashboard.thisWeek')}</span>
|
||||
</Flex>
|
||||
</Tag>)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Divider } from 'antd';
|
||||
// import arrowRight from '@/assets/images/index/arrow_right.svg'
|
||||
import { Flex } from 'antd';
|
||||
|
||||
import { getVersion, type versionResponse } from '@/api/common'
|
||||
|
||||
const GuideCard: React.FC = () => {
|
||||
const VersionCard: React.FC = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [versionInfo, setVersionInfo] = useState<versionResponse | null>(null);
|
||||
|
||||
@@ -23,13 +23,6 @@ const GuideCard: React.FC = () => {
|
||||
return currentLang === 'zh' ? versionInfo.introduction : (versionInfo.introduction_en || versionInfo.introduction);
|
||||
};
|
||||
|
||||
// 解析换行符和HTML的方法
|
||||
const parseContent = (text: string) => {
|
||||
if (!text) return '';
|
||||
// 将 \n 转换为 <br/> 标签
|
||||
return text.replace(/\\n/g, '<br/>');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVersion = async () => {
|
||||
try {
|
||||
@@ -44,58 +37,36 @@ const GuideCard: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='rb:w-full rb:p-4 rb:border-1 rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded-xl'>
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:text-[#5B6167] rb:text-base rb:font-semibold rb:gap-2'>
|
||||
{ t('index.latestUpdate')}
|
||||
<span className='rb:text-xs rb:text-[#1890FF]'>
|
||||
{versionInfo?.version}
|
||||
</span>
|
||||
</div>
|
||||
<div className='rb:flex rb:flex-col rb:max-h-[400px] rb:overflow-y-auto rb:text-[#5B6167]'>
|
||||
{versionInfo && (() => {
|
||||
const introduction = getIntroduction();
|
||||
return introduction ? (<>
|
||||
<div className='rb:flex rb:items-center rb:gap-2 rb:text-sm rb:text-[#5B6167] rb:leading-5 '>
|
||||
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.releaseDate')}: {introduction.releaseDate}
|
||||
</span>
|
||||
<Divider type='vertical' />
|
||||
<span className='rb:text-xs rb:text-[#5B6167]'>
|
||||
{t('version.name')}: {introduction.codeName}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className='rb:text-sm rb:text-[#5B6167] rb:leading-5 rb:mt-2'
|
||||
dangerouslySetInnerHTML={{ __html: introduction.upgradePosition }}
|
||||
/>
|
||||
{introduction.coreUpgrades?.map((item: string, index: number) => (
|
||||
<p
|
||||
key={index}
|
||||
className='rb:text-sm rb:text-[#5B6167] rb:leading-5'
|
||||
dangerouslySetInnerHTML={{ __html: item }}
|
||||
/>
|
||||
))}
|
||||
</>) : null;
|
||||
})()}
|
||||
{/* {loading ? (
|
||||
t('index.loading')
|
||||
) : (
|
||||
versionInfo?.introduction || t('index.latestUpdateDesc')
|
||||
)} */}
|
||||
</div>
|
||||
{/* <div className='rb:flex rb:w-full rb:items-center rb:justify-between rb:gap-3 rb:mt-4'>
|
||||
<Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#212332] '>
|
||||
<span className='rb:text-xs'>{ t('index.viewDetails')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
<Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#212332]'>
|
||||
<span className='rb:text-xs'>{ t('index.changeLog')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
</div> */}
|
||||
<div className='rb:w-full rb:p-3 rb:bg-white rb:rounded-xl rb:mt-3'>
|
||||
<Flex gap={4} className="rb:mb-3">
|
||||
<span className="rb:font-[MiSans-Bold] rb:font-bold rb:leading-5">{t('index.latestUpdate')}</span>
|
||||
<span className='rb:text-[12px] rb:text-white rb:leading-4.25 rb:pt-px rb:pl-2 rb:pr-1.75 rb:bg-[#171719] rb:rounded-lg rb:rounded-bl-none '>
|
||||
{versionInfo?.version}
|
||||
</span>
|
||||
</Flex>
|
||||
{versionInfo && (() => {
|
||||
const introduction = getIntroduction();
|
||||
return introduction ? (<>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:mt-1 rb:mb-2">
|
||||
{t('version.releaseDate')}: {introduction.releaseDate} | {t('version.name')}: {introduction.codeName}
|
||||
</div>
|
||||
<div className="rb:max-h-76 rb:overflow-y-auto">
|
||||
<p
|
||||
className='rb:text-[12px] rb:leading-4.5'
|
||||
dangerouslySetInnerHTML={{ __html: introduction.upgradePosition }}
|
||||
/>
|
||||
{introduction.coreUpgrades?.map((item: string, index: number) => (
|
||||
<p
|
||||
key={index}
|
||||
className='rb:text-[12px] rb:leading-4.5 rb:mt-2'
|
||||
dangerouslySetInnerHTML={{ __html: item }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>) : null;
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideCard;
|
||||
export default VersionCard;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Space, Button } from 'antd';
|
||||
import { Space, Button, Row, Col, Flex } from 'antd';
|
||||
|
||||
import TopCardList from './components/TopCardList';
|
||||
import GuideCard from './components/GuideCard';
|
||||
import VersionCard from './components/VersionCard';
|
||||
import QuickActions from './components/QuickActions';
|
||||
import bgImg from '@/assets/images/index/index_bg@2x.png'
|
||||
import Table, { type TableRef } from '@/components/Table'
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
@@ -42,16 +42,15 @@ const Index = () => {
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
|
||||
{
|
||||
title: t('space.spaceIcon'),
|
||||
dataIndex: 'icon',
|
||||
key: 'icon',
|
||||
render:(value: string, record: any) => {
|
||||
return value ? (
|
||||
<img src={value} alt="icon" className='rb:w-[24px] rb:h-[24px]' />
|
||||
<img src={value} alt="icon" className='rb:size-6' />
|
||||
) : (
|
||||
<div className='rb:w-[24px] rb:h-[24px] rb:bg-blue-500 rb:text-white rb:rounded rb:flex rb:items-center rb:justify-center rb:text-xs rb:font-medium'>
|
||||
<div className='rb:size-6 rb:bg-[#155EEF] rb:text-white rb:rounded rb:flex rb:items-center rb:justify-center rb:text-xs rb:font-medium'>
|
||||
{record.name?.charAt(0)?.toUpperCase() || '?'}
|
||||
</div>
|
||||
)
|
||||
@@ -84,7 +83,7 @@ const Index = () => {
|
||||
width: 100,
|
||||
render: (_, record) => (
|
||||
<Space size="middle">
|
||||
<Button onClick={() => handleJump(record.id)} color="primary" variant="text">{t('space.enterSpace')}</Button>
|
||||
<Button type="link" onClick={() => handleJump(record.id)}>{t('space.enterSpace')}</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -99,44 +98,40 @@ const Index = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="rb:pb-[24px]">
|
||||
<div className="rb:mt-[16px] rb:flex rb:gap-4">
|
||||
<div className='rb:flex-1'>
|
||||
<div className='rb:flex-col rb:w-full rb:h-[120px] rb:mb-4 rb:p-6 rb:leading-[30px]' style={{backgroundImage: `url(${bgImg})`, backgroundSize: '100% 100%'}}>
|
||||
<div className='rb:flex rb:text-[22px] rb:text-[#0041C3] rb:font-semibold'>
|
||||
{ t('index.spaceTitle' )}
|
||||
</div>
|
||||
<div className='rb:flex rb:mt-2 rb:text-xs rb:leading-[18px] rb:text-[#5F6266] rb:max-w-[560px]'>
|
||||
{ t('index.spaceSubTitle' )}
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
<Col flex="1">
|
||||
<Flex vertical>
|
||||
<div className='rb:w-full rb:h-26 rb:p-4 rb:bg-cover rb:bg-[url("@/assets/images/index/index_bg@2x.png")]'>
|
||||
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-white rb:text-[18px] rb:leading-7">
|
||||
{t('index.spaceTitle')}
|
||||
</div>
|
||||
<div className='rb:mt-2 rb:text-[12px] rb:leading-4.5 rb:text-white rb:max-w-139.75'>
|
||||
{t('index.spaceSubTitle')}
|
||||
</div>
|
||||
</div>
|
||||
{/* 统计卡片 */}
|
||||
<TopCardList data={dashboardData} />
|
||||
<div className="rb:rounded rb:max-h-[calc(100%-100px)] rb:overflow-y-auto rb:mt-4">
|
||||
<div className="rb:rounded-xl rb:bg-white rb:pt-3 rb:px-3 rb:overflow-y-hidden rb:my-3 rb:flex-1">
|
||||
<Table
|
||||
ref={tableRef}
|
||||
apiUrl={tableApi}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
|
||||
bordered={false}
|
||||
scrollY="100%"
|
||||
className="rb:-mb-3!"
|
||||
// scroll={{ y: 'calc(100vh - 340px)' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rb:flex-0 rb:min-w-80'>
|
||||
{/* 引导 */}
|
||||
<GuideCard />
|
||||
<div className='rb:w-full rb:mt-4 '>
|
||||
<VersionCard />
|
||||
</div>
|
||||
{/* 快捷操作 */}
|
||||
<div className='rb:w-full rb:mt-4'>
|
||||
<QuickActions onNavigate={navigate} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</Col>
|
||||
<Col flex="328px">
|
||||
{/* 引导 */}
|
||||
<GuideCard />
|
||||
<VersionCard />
|
||||
<QuickActions onNavigate={navigate} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:00
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 16:50:00
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-20 18:50:41
|
||||
*/
|
||||
/**
|
||||
* Group Model View
|
||||
@@ -12,14 +12,15 @@
|
||||
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import clsx from 'clsx'
|
||||
import { Button } from 'antd'
|
||||
import { Button, Flex, Tooltip, Space } from 'antd'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ProviderModelItem, ModelListItem, DescriptionItem, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getModelNewList } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
/**
|
||||
* Group model list component
|
||||
@@ -50,11 +51,6 @@ const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem
|
||||
label: t(`modelNew.type`),
|
||||
children: data.type ? t(`modelNew.${data.type}`) : '-',
|
||||
},
|
||||
{
|
||||
key: 'is_active',
|
||||
label: t(`modelNew.status`),
|
||||
children: data.is_active ? t(`common.statusEnabled`) : t(`common.statusDisabled`),
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
label: t(`modelNew.created_at`),
|
||||
@@ -73,31 +69,36 @@ const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
:(
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-3">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
avatarUrl={item.logo}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
avatarText={item.name[0]}
|
||||
title={<Flex vertical gap={6}>
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
<Space>
|
||||
<Tag color={item.is_active ? 'success' : 'error'}>{item.is_active ? t(`common.statusEnabled`) : t(`common.statusDisabled`)}</Tag>
|
||||
</Space>
|
||||
</Flex>}
|
||||
isNeedTooltip={false}
|
||||
footer={<Button className="rb:h-9!" type="primary" ghost block onClick={() => handleEdit(item)}>{t('modelNew.configureBtn')}</Button>}
|
||||
>
|
||||
{formatData(item)?.map((description: DescriptionItem) => (
|
||||
<div
|
||||
key={description.key}
|
||||
className="rb:flex rb:justify-between rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap">{(description.label as string)}</span>
|
||||
<span className={clsx({
|
||||
"rb:text-[#212332]": description.key !== 'is_active',
|
||||
"rb:text-[#369F21] rb:font-medium": description.key === 'is_active' && item.is_active,
|
||||
})}>{(description.children as string)}</span>
|
||||
</div>
|
||||
))}
|
||||
<Button className="rb:mt-2" type="primary" ghost block onClick={() => handleEdit(item)}>{t('modelNew.configureBtn')}</Button>
|
||||
<Flex vertical gap={8}>
|
||||
{formatData(item)?.map((description: DescriptionItem) => (
|
||||
<div
|
||||
key={description.key}
|
||||
className="rb:flex rb:justify-between rb:text-[14px] rb:leading-5"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap rb:text-[#5B6167]">{(description.label as string)}</span>
|
||||
<span className={clsx({
|
||||
"rb:font-medium": description.key === 'type',
|
||||
})}>{(description.children as string)}</span>
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:10
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-27 10:20:51
|
||||
* @Last Modified time: 2026-03-20 18:51:27
|
||||
*/
|
||||
/**
|
||||
* Model List View
|
||||
@@ -11,11 +11,11 @@
|
||||
*/
|
||||
|
||||
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { Button, Flex, Row, Col } from 'antd'
|
||||
import { Button, Flex, Row, Col, Tooltip, Space } from 'antd'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ProviderModelItem, KeyConfigModalRef, ModelListDetailRef, ModelListItem, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getModelNewList } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import Tag from '@/components/Tag';
|
||||
@@ -69,26 +69,26 @@ const ModelList = forwardRef<BaseRef, { query: any; handleEdit: (vo?: ModelListI
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.provider}
|
||||
title={t(`modelNew.${item.provider}`)}
|
||||
avatarUrl={getListLogoUrl(item.provider, item.logo)}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.provider[0].toUpperCase()}
|
||||
</div>
|
||||
}
|
||||
bodyClassName="rb:relative rb:pb-[64px]! rb:h-[calc(100%-64px)]!"
|
||||
avatarText={item.provider[0].toUpperCase()}
|
||||
title={<Flex vertical gap={6}>
|
||||
<Tooltip title={t(`modelNew.${item.provider}`)}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{t(`modelNew.${item.provider}`)}</div>
|
||||
</Tooltip>
|
||||
<Space>
|
||||
<Flex gap={8} wrap>{item.tags.map(tag => <Tag key={tag}>{t(`modelNew.${tag}`)}</Tag>)}</Flex>
|
||||
</Space>
|
||||
</Flex>}
|
||||
isNeedTooltip={false}
|
||||
footer={<Row gutter={9} className="rb:pt-2!">
|
||||
<Col span={12}>
|
||||
<Button className="rb:h-9!" block onClick={() => handleShowModel(item)}>{t('modelNew.showModel')}</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button className="rb:h-9!" type="primary" ghost block onClick={() => handleKeyConfig(item)}>{t('modelNew.keyConfig')}</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
>
|
||||
<Flex gap={8} wrap>{item.tags.map(tag => <Tag key={tag}>{t(`modelNew.${tag}`)}</Tag>)}</Flex>
|
||||
<div className="rb:absolute rb:bottom-4 rb:left-6 rb:right-6">
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Button block onClick={() => handleShowModel(item)}>{t('modelNew.showModel')}</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button type="primary" ghost block onClick={() => handleKeyConfig(item)}>{t('modelNew.keyConfig')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 16:50:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-23 11:33:44
|
||||
*/
|
||||
/**
|
||||
* Model Square View
|
||||
@@ -10,17 +10,17 @@
|
||||
* Allows adding models and viewing details
|
||||
*/
|
||||
|
||||
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { Button, Space, App, Divider, Flex, Tooltip } from 'antd'
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { Button, Space, App, Flex, Tooltip } from 'antd'
|
||||
import { UsergroupAddOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import type { ModelPlaza, ModelPlazaItem, BaseRef } from './types'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getModelPlaza, addModelPlaza } from '@/api/models'
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import Tag from '@/components/Tag';
|
||||
import ModelSquareDetail from './components/ModelSquareDetail'
|
||||
import { getLogoUrl } from './utils'
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,6 @@ import { getLogoUrl } from './utils'
|
||||
const ModelSquare = forwardRef <BaseRef, { query: any; }>(({ query }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const modelSquareDetailRef = useRef<ModelSquareDetailRef>(null)
|
||||
const [list, setList] = useState<ModelPlaza[]>([])
|
||||
useEffect(() => {
|
||||
getList()
|
||||
@@ -38,14 +37,12 @@ const ModelSquare = forwardRef <BaseRef, { query: any; }>(({ query }, ref) => {
|
||||
const getList = () => {
|
||||
getModelPlaza(query)
|
||||
.then(res => {
|
||||
setList((res as ModelPlaza[]) || [])
|
||||
const response = res as ModelPlaza[]
|
||||
setList(response || [])
|
||||
setActiveProvider(response[0]?.provider || null)
|
||||
})
|
||||
}
|
||||
|
||||
/** Open model detail drawer */
|
||||
const handleMore = (vo: ModelPlaza) => {
|
||||
modelSquareDetailRef.current?.handleOpen(vo)
|
||||
}
|
||||
/** Add model to workspace */
|
||||
const handleAdd = (item: ModelPlazaItem) => {
|
||||
addModelPlaza(item.id)
|
||||
@@ -59,61 +56,86 @@ const ModelSquare = forwardRef <BaseRef, { query: any; }>(({ query }, ref) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
getList,
|
||||
}));
|
||||
|
||||
const [activeProvider, setActiveProvider] = useState<string | null>(null)
|
||||
return (
|
||||
<>
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
: list.map(vo => (
|
||||
<div key={vo.provider}>
|
||||
<div className="rb:flex rb:justify-between rb:items-center rb:bg-[rgba(21,94,239,0.12)] rb:px-4 rb:py-2.5 rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">
|
||||
<div className="rb:font-medium">{t(`modelNew.${vo.provider}`)}</div>
|
||||
<Button type="link" onClick={() => handleMore(vo)}>{t('modelNew.viewAll')}({t(`modelNew.modelCount`, { count: vo.models.length })})></Button>
|
||||
</div>
|
||||
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-4">
|
||||
{vo.models.slice(0, 6).map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
subTitle={<Space size={8}>
|
||||
<Tag className="rb:mt-1">{t(`modelNew.${item.type}`)}</Tag>
|
||||
{item.is_official && <Tag color="success" className="rb:mt-1">{t(`modelNew.official`)}</Tag>}
|
||||
</Space>}
|
||||
avatarUrl={getLogoUrl(item.logo)}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
bodyClassName="rb:relative rb:pb-[80px]! rb:h-[calc(100%-64px)]!"
|
||||
>
|
||||
<Tooltip title={item.description}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:font-regular rb:wrap-break-word rb:line-clamp-2 rb:mt-3">{item.description}</div>
|
||||
</Tooltip>
|
||||
<Flex gap={8} wrap className="rb:mt-3!">{item.tags.map((tag, tagIndex) => <Tag key={tagIndex}>{tag}</Tag>)}</Flex>
|
||||
<div className="rb:absolute rb:bottom-4 rb:left-6 rb:right-6">
|
||||
<Divider size="middle" />
|
||||
<Flex justify="space-between">
|
||||
<Space size={8}><UsergroupAddOutlined /> {item.add_count}</Space>
|
||||
<Space>
|
||||
{item.is_added
|
||||
? <Button type="primary" disabled>{t('modelNew.added')}</Button>
|
||||
: <Button type="primary" ghost disabled={item.is_deprecated} onClick={() => handleAdd(item)}>{item.is_deprecated ? t('modelNew.deprecated') : `+ ${t('common.add')}`}</Button>
|
||||
}
|
||||
</Space>
|
||||
: <>
|
||||
<Space size={8} className="rb:mb-3!">
|
||||
{list.map(vo => (
|
||||
<div
|
||||
key={vo.provider}
|
||||
className={clsx('rb:border rb:border-[#171719] rb:rounded-full rb:px-2 rb:py-1 rb:cursor-pointer', {
|
||||
'rb:text-white rb:bg-[#171719]': activeProvider === vo.provider,
|
||||
'rb:text-[#171719]': activeProvider === vo.provider,
|
||||
})}
|
||||
onClick={() => setActiveProvider(vo.provider)}
|
||||
>{t(`modelNew.${vo.provider}`)}</div>
|
||||
))}
|
||||
</Space>
|
||||
{list.filter(vo => vo.provider === activeProvider).map(vo => (
|
||||
<div key={vo.provider} className="rb:max-h-[calc(100%-50px)] rb:overflow-y-auto">
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-4">
|
||||
{vo.models.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
avatarUrl={getLogoUrl(item.logo)}
|
||||
avatarText={item.name[0]}
|
||||
title={
|
||||
<Flex justify="space-between" gap={16}>
|
||||
<Flex vertical gap={6}>
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
<Space size={8} className="rb:mt-1!">
|
||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||
{item.is_official && <Tag color="success">{t(`modelNew.official`)}</Tag>}
|
||||
</Space>
|
||||
</Flex>
|
||||
<Button
|
||||
size="small"
|
||||
disabled={item.is_added || item.is_deprecated}
|
||||
onClick={() => handleAdd(item)}
|
||||
>{item.is_deprecated ? t('modelNew.deprecated') : '+'}</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
}
|
||||
isNeedTooltip={false}
|
||||
footer={<Flex justify="space-between" align="center" className="rb:text-[#5B6167] rb:text-[12px]">
|
||||
@{t(`modelNew.${vo.provider}`)}
|
||||
<Space size={4}><UsergroupAddOutlined /> {item.add_count}</Space>
|
||||
</Flex>}
|
||||
>
|
||||
<Tooltip title={item.description}>
|
||||
<div className="rb:h-10 rb:leading-5 rb:wrap-break-word rb:line-clamp-2">{item.description}</div>
|
||||
</Tooltip>
|
||||
|
||||
<ModelSquareDetail
|
||||
ref={modelSquareDetailRef}
|
||||
refresh={getList}
|
||||
/>
|
||||
<Flex gap={8} wrap align="center" className="rb:mt-2!">
|
||||
<Flex gap={6}>
|
||||
{item.tags?.slice(0, 2).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">{type}</div>
|
||||
))}
|
||||
</Flex>
|
||||
{item.tags.length > 2 && (
|
||||
<Tooltip
|
||||
title={<Flex wrap gap={6}>{item.tags?.slice(2, item.tags.length).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5 rb:text-[#171719]">{type}</div>
|
||||
))}</Flex>}
|
||||
color="white"
|
||||
placement="bottom"
|
||||
>
|
||||
<div className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">+{item.tags.length - 2}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Flex>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:49:49
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-04 11:50:31
|
||||
*/
|
||||
/**
|
||||
* Model Square Detail Drawer
|
||||
* Displays all models from a specific provider in the model square
|
||||
* Allows adding models and editing custom models
|
||||
*/
|
||||
|
||||
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Space, App, Flex, Tooltip, Divider } from 'antd'
|
||||
import { UsergroupAddOutlined } from '@ant-design/icons';
|
||||
|
||||
import type { ModelPlaza, ModelPlazaItem, ModelSquareDetailRef } from '../types';
|
||||
import RbDrawer from '@/components/RbDrawer';
|
||||
import { getModelPlaza, addModelPlaza } from '@/api/models'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import Tag from '@/components/Tag';
|
||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||
import { getLogoUrl } from '../utils'
|
||||
|
||||
/**
|
||||
* Component props
|
||||
*/
|
||||
interface ModelSquareDetailProps {
|
||||
/** Callback to refresh parent list */
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model square detail drawer component
|
||||
*/
|
||||
const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProps>(({ refresh }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const [model, setModel] = useState<ModelPlaza>({} as ModelPlaza)
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [list, setList] = useState<ModelPlazaItem[]>([])
|
||||
|
||||
/** Open drawer with model plaza data */
|
||||
const handleOpen = (vo: ModelPlaza) => {
|
||||
setModel(vo)
|
||||
setOpen(true)
|
||||
getList(vo)
|
||||
}
|
||||
/** Close drawer */
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
refresh()
|
||||
}
|
||||
/** Fetch model list for provider */
|
||||
const getList = (vo: ModelPlaza) => {
|
||||
getModelPlaza({ provider: vo.provider })
|
||||
.then(res => {
|
||||
const response = res as ModelPlaza[]
|
||||
setList(response.length > 0 ? response[0].models : [])
|
||||
})
|
||||
}
|
||||
/** Add model to workspace */
|
||||
const handleAdd = (item: ModelPlazaItem) => {
|
||||
addModelPlaza(item.id)
|
||||
.then(() => {
|
||||
message.success(`${item.name}${t('modelNew.addSuccess')}`)
|
||||
getList(model)
|
||||
})
|
||||
}
|
||||
|
||||
/** Expose methods to parent component */
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbDrawer
|
||||
title={<>{t(`modelNew.${model.provider}`)} {t('modelNew.modelList')} ({list.length}{t('modelNew.item')})</>}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<div className="rb:h-full rb:overflow-y-auto">
|
||||
{list.length === 0
|
||||
? <PageEmpty />
|
||||
: <div className="rb:grid rb:grid-cols-2 rb:gap-4">
|
||||
{list.map(item => (
|
||||
<RbCard
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
subTitle={<Space size={8} className="rb:mt-1!">
|
||||
<Tag>{t(`modelNew.${item.type}`)}</Tag>
|
||||
{item.is_official && <Tag color="success">{t(`modelNew.official`)}</Tag>}
|
||||
{item.capability?.filter(item => item !== 'video').map(vo => <Tag key={vo}>{t(`modelNew.${vo}`)}</Tag>)}
|
||||
</Space>}
|
||||
avatarUrl={getLogoUrl(item.logo)}
|
||||
avatar={
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
bodyClassName="rb:relative rb:pb-[80px]! rb:h-[calc(100%-64px)]!"
|
||||
>
|
||||
<Tooltip title={item.description}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:font-regular rb:wrap-break-word rb:line-clamp-2 rb:mt-3">{item.description}</div>
|
||||
</Tooltip>
|
||||
<Flex gap={8} wrap className="rb:mt-3!">{item.tags.map((tag, tagIndex) => <Tag key={tagIndex}>{tag}</Tag>)}</Flex>
|
||||
<div className="rb:absolute rb:bottom-4 rb:left-6 rb:right-6">
|
||||
<Divider size="middle" />
|
||||
<Flex justify="space-between">
|
||||
<Space size={8}><UsergroupAddOutlined /> {item.add_count}</Space>
|
||||
<Space>
|
||||
{item.is_added
|
||||
? <Button type="primary" disabled>{t('modelNew.added')}</Button>
|
||||
: <Button type="primary" ghost disabled={item.is_deprecated} onClick={() => handleAdd(item)}>{item.is_deprecated ? t('modelNew.deprecated') : `+ ${t('common.add')}`}</Button>
|
||||
}
|
||||
</Space>
|
||||
</Flex>
|
||||
</div>
|
||||
</RbCard>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</RbDrawer>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModelSquareDetail;
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:05
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 16:50:05
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-20 19:02:31
|
||||
*/
|
||||
/**
|
||||
* Model Management Main Page
|
||||
@@ -84,7 +84,7 @@ const tabKeys = ['group', 'list', 'square']
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex vertical gap={16}>
|
||||
<Flex justify="space-between" align="center">
|
||||
<PageTabs
|
||||
value={activeTab}
|
||||
@@ -100,19 +100,19 @@ const tabKeys = ['group', 'list', 'square']
|
||||
url={modelTypeUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
className="rb:w-30"
|
||||
className="rb:w-40"
|
||||
allowClear={true}
|
||||
placeholder={t('modelNew.type')}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
{(activeTab === 'list' || activeTab === 'square') &&
|
||||
{activeTab === 'list' &&
|
||||
<Form.Item name="provider" noStyle>
|
||||
<CustomSelect
|
||||
url={modelProviderUrl}
|
||||
hasAll={false}
|
||||
format={(items) => items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))}
|
||||
className="rb:w-30"
|
||||
className="rb:w-40"
|
||||
allowClear={true}
|
||||
placeholder={t('modelNew.provider')}
|
||||
/>
|
||||
@@ -123,7 +123,6 @@ const tabKeys = ['group', 'list', 'square']
|
||||
<SearchInput
|
||||
maxLength={50}
|
||||
placeholder={t(`modelNew.${activeTab}SearchPlaceholder`)}
|
||||
className="rb:w-70!"
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
@@ -133,7 +132,7 @@ const tabKeys = ['group', 'list', 'square']
|
||||
</Form>
|
||||
</Flex>
|
||||
|
||||
<div className="rb:w-full rb:h-[calc(100%-48px)] rb:my-4">
|
||||
<div className="rb:w-full rb:h-[calc(100vh-125px)] rb:overflow-y-auto">
|
||||
{activeTab === 'group' && <GroupModel ref={groupRef} query={query} handleEdit={handleEdit} />}
|
||||
{activeTab === 'list' && <ModelList ref={modelListRef} query={query} handleEdit={handleEdit} />}
|
||||
{activeTab === 'square' && <ModelSquare query={query} />}
|
||||
@@ -146,7 +145,7 @@ const tabKeys = ['group', 'list', 'square']
|
||||
ref={customModelModalRef}
|
||||
refresh={handleRefresh}
|
||||
/>
|
||||
</>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:18
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-07 16:14:25
|
||||
* @Last Modified time: 2026-03-20 20:21:45
|
||||
*/
|
||||
/**
|
||||
* Type definitions for Model Management
|
||||
@@ -270,14 +270,6 @@ export interface ModelPlazaItem {
|
||||
is_omni?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model square detail ref interface
|
||||
*/
|
||||
export interface ModelSquareDetailRef {
|
||||
/** Open detail drawer with model plaza data */
|
||||
handleOpen: (vo: ModelPlaza) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom model form data
|
||||
*/
|
||||
|
||||
@@ -2,23 +2,25 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 14:10:15
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-06 10:56:44
|
||||
* @Last Modified time: 2026-03-20 16:36:02
|
||||
*/
|
||||
import { type FC, useState, useRef, type MouseEvent } from 'react';
|
||||
import { type FC, useState, useRef } from 'react';
|
||||
import type { MenuInfo } from 'rc-menu/lib/interface';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Row, Col, Button, Flex, Divider, Space, App, Tooltip } from 'antd'
|
||||
import { Row, Col, Flex, Space, App, Tooltip, Dropdown } from 'antd'
|
||||
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
import OntologyModal from './components/OntologyModal'
|
||||
import type { OntologyModalRef, OntologyItem, Query, OntologyImportModalRef, OntologyExportModalRef } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import Tag from '@/components/Tag'
|
||||
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
||||
import { getOntologyScenesUrl, deleteOntologyScene } from '@/api/ontology'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import OntologyImportModal from './components/OntologyImportModal'
|
||||
import OntologyExportModal from './components/OntologyExportModal'
|
||||
import RbButton from '@/components/RbButton'
|
||||
|
||||
/**
|
||||
* Ontology management page component
|
||||
@@ -51,20 +53,18 @@ const Ontology: FC = () => {
|
||||
* @param record - The ontology item to edit
|
||||
* @param e - Mouse event to prevent propagation
|
||||
*/
|
||||
const handleEdit = (record: OntologyItem, e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const handleEdit = (record: OntologyItem, e: MenuInfo) => {
|
||||
e.domEvent.stopPropagation();
|
||||
entityModalRef.current?.handleOpen(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an ontology scene with confirmation
|
||||
* @param item - The ontology item to delete
|
||||
* @param e - Mouse event to prevent propagation
|
||||
* @param e - Menu click info
|
||||
*/
|
||||
const handleDelete = (item: OntologyItem, e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const handleDelete = (item: OntologyItem, e: MenuInfo) => {
|
||||
e.domEvent.stopPropagation();
|
||||
modal.confirm({
|
||||
title: t('common.confirmDeleteDesc', { name: item.scene_name }),
|
||||
okText: t('common.delete'),
|
||||
@@ -111,28 +111,23 @@ const Ontology: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className="rb:mb-4">
|
||||
<Col span={8}>
|
||||
<SearchInput
|
||||
placeholder={t('ontology.searchPlaceholder')}
|
||||
onSearch={(value) => setQuery({ scene_name: value })}
|
||||
className="rb:w-full!"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={16} className="rb:text-right">
|
||||
<Space size={12}>
|
||||
<Button onClick={handleExport}>
|
||||
{t('ontology.export')}
|
||||
</Button>
|
||||
<Button onClick={handleImport}>
|
||||
{t('ontology.import')}
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleCreate}>
|
||||
+ {t('ontology.create')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
<Flex align="center" justify="space-between" className="rb:mb-4!">
|
||||
<SearchInput
|
||||
placeholder={t('ontology.searchPlaceholder')}
|
||||
onSearch={(value) => setQuery({ scene_name: value })}
|
||||
/>
|
||||
<Space size={12}>
|
||||
<RbButton ghost type="primary" onClick={handleExport}>
|
||||
{t('ontology.export')}
|
||||
</RbButton>
|
||||
<RbButton ghost type="primary" onClick={handleImport}>
|
||||
{t('ontology.import')}
|
||||
</RbButton>
|
||||
<RbButton type="primary" onClick={handleCreate}>
|
||||
+ {t('ontology.create')}
|
||||
</RbButton>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<PageScrollList<OntologyItem, Query>
|
||||
ref={scrollListRef}
|
||||
@@ -141,58 +136,70 @@ const Ontology: FC = () => {
|
||||
column={3}
|
||||
renderItem={(item) =>(
|
||||
<RbCard
|
||||
title={item.scene_name}
|
||||
extra={<Tag>{item.type_num} {t('ontology.typeCount')}</Tag>}
|
||||
onClick={() => handleJump(item)}
|
||||
className="rb:cursor-pointer rb:relative"
|
||||
>
|
||||
{item.is_system_default &&
|
||||
<div className="rb:absolute rb:-right-px rb:-top-px rb:bg-[#FF5D34] rb:rounded-[0px_7px_0px_8px] rb:text-[12px] rb:text-white rb:font-regular rb:leading-4 rb:py-0.5 rb:px-1">
|
||||
{t('common.default')}
|
||||
</div>
|
||||
title={
|
||||
<Flex justify="space-between">
|
||||
<Flex gap={4} vertical>
|
||||
{item.scene_name}
|
||||
<Space size={8}>
|
||||
<Tag>{item.type_num} {t('ontology.typeCount')}</Tag>
|
||||
{item.is_system_default && <Tag color="warning">{t('common.default')}</Tag>}
|
||||
</Space>
|
||||
</Flex>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'edit',
|
||||
icon: <div className="rb:size-6 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit.svg')]" />,
|
||||
label: t('common.edit'),
|
||||
onClick: (e: MenuInfo) => handleEdit(item, e),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
icon: <div className="rb:size-6 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/delete.svg')]" />,
|
||||
label: t('common.delete'),
|
||||
onClick: (e: MenuInfo) => handleDelete(item, e),
|
||||
},
|
||||
]
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="rb:cursor-pointer rb:size-6 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]"></div>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
}
|
||||
<div
|
||||
className="rb:flex rb:gap-2 rb:justify-between rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap">{t(`ontology.scene_description`)}</span>
|
||||
<Tooltip title={item.scene_description} placement="topRight">
|
||||
<span className="rb:font-medium rb:flex-1 rb:text-right rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{item.scene_description}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{(['created_at', 'updated_at'] as const).map(key => (
|
||||
<div
|
||||
key={key}
|
||||
className="rb:flex rb:gap-2 rb:justify-between rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<span className="rb:whitespace-nowrap">{t(`ontology.${key}`)}</span>
|
||||
<span className="rb:font-medium">{formatDateTime(item[key])}</span>
|
||||
</div>
|
||||
))}
|
||||
<Divider size="middle" />
|
||||
<Flex gap={8} wrap align="center">
|
||||
<div className="rb:text-[#5B6167] rb:leading-4.5">{t('ontology.entityTypes')}: </div>
|
||||
<div className="rb:flex-1 rb:overflow-hidden rb:wrap-break-word! rb:line-clamp-1!">
|
||||
isNeedTooltip={false}
|
||||
headerClassName="rb:pb-0!"
|
||||
onClick={() => handleJump(item)}
|
||||
className="rb:cursor-pointer!"
|
||||
>
|
||||
<Tooltip title={item.scene_description}>
|
||||
<div className="rb:h-10 rb:wrap-break-word rb:line-clamp-2 rb:leading-5">{item.scene_description}</div>
|
||||
</Tooltip>
|
||||
|
||||
<Flex gap={8} wrap align="center" className="rb:mt-2!">
|
||||
<Flex gap={8} className="rb:flex-1 rb:overflow-hidden rb:wrap-break-word! rb:line-clamp-1!">
|
||||
{item.entity_type?.map((type, i) => (
|
||||
<Tag key={i} color={i % 2 ? 'processing' : 'success'} className="rb:ml-2">{type}</Tag>
|
||||
<span key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">{type}</span>
|
||||
))}
|
||||
</div>
|
||||
</Flex>
|
||||
{item.type_num > 3 && (
|
||||
<Tag color="default">+{item.type_num - 3}</Tag>
|
||||
<span className="rb:bg-[#F6F6F6] rb:rounded-full rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">+{item.type_num - 3}</span>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<div className="rb:mt-4 rb:h-5 rb:text-[12px] rb:leading-4 rb:font-regular rb:text-[#5B6167] rb:flex rb:items-center rb:justify-end">
|
||||
{!item.is_system_default && <Space size={16}>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
onClick={(e) => handleEdit(item, e)}
|
||||
></div>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={(e) => handleDelete(item, e)}
|
||||
></div>
|
||||
</Space>}
|
||||
</div>
|
||||
<Row className="rb:mt-4!">
|
||||
{(['created_at', 'updated_at'] as const).map(key => (
|
||||
<Col
|
||||
key={key}
|
||||
span={12}
|
||||
className="rb:text-[#5B6167] rb:text-[12px]! rb:leading-4.5"
|
||||
>
|
||||
<div>{t(`ontology.${key}`)}</div>
|
||||
<div>{formatDateTime(item[key])}</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</RbCard>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 14:10:20
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-06 11:26:49
|
||||
* @Last Modified time: 2026-03-20 16:35:14
|
||||
*/
|
||||
import { type FC, useEffect, useState, useRef } from 'react'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { App, Row, Col, Tooltip, Space, Button } from 'antd'
|
||||
import { App, Row, Col, Tooltip, Space, Button, Flex } from 'antd'
|
||||
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import PageHeader from '@/components/Layout/PageHeader'
|
||||
import { getOntologyClassList, deleteOntologyClass } from '@/api/ontology'
|
||||
import type { OntologyClassData, OntologyClassModalRef, OntologyClassExtractModalRef, OntologyClassItem } from '@/views/Ontology/types'
|
||||
import RbCard from '@/components/RbCard/Card';
|
||||
import RbCard from '@/components/RbCard';
|
||||
import OntologyClassModal from '../components/OntologyClassModal'
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
import OntologyClassExtractModal from '../components/OntologyClassExtractModal'
|
||||
@@ -26,6 +26,7 @@ import Tag from '@/components/Tag'
|
||||
const Detail: FC = () => {
|
||||
// Hooks
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams()
|
||||
const { modal, message } = App.useApp()
|
||||
|
||||
@@ -100,19 +101,29 @@ const Detail: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
name={<Space>
|
||||
title={<Space>
|
||||
{data.scene_name}
|
||||
{data.is_system_default ? <Tag color="warning">{t('common.default')}</Tag> : undefined}
|
||||
<Tooltip title={data.scene_description}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>
|
||||
</Space>}
|
||||
extra={<Space size={12}>
|
||||
{data.is_system_default ? undefined : (<Space>
|
||||
<Button type="primary" ghost className="rb:h-6! rb:px-2! rb:leading-5.5!" onClick={handleAdd}>+ {t('ontology.addClass')}</Button>
|
||||
<Button className="rb:h-6! rb:px-2! rb:leading-5.5!" type="primary" onClick={handleExtract}>+ {t('ontology.extract')}</Button>
|
||||
</Space>)}
|
||||
<Flex align="center" className="rb:leading-5 rb:text-[14px] rb:text-[#5B6167] rb:font-regular rb:cursor-pointer" onClick={() => navigate(-1)}>
|
||||
<div
|
||||
className="rb:mr-2 rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/logout.svg')]"
|
||||
></div>
|
||||
{t('common.return')}
|
||||
</Flex>
|
||||
</Space>}
|
||||
subTitle={<Tooltip title={data.scene_description}><div className="rb:h-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{data.scene_description}</div></Tooltip>}
|
||||
extra={data.is_system_default ? undefined : (<Space>
|
||||
<Button type="primary" ghost className="rb:h-6! rb:px-2! rb:leading-5.5!" onClick={handleAdd}>+ {t('ontology.addClass')}</Button>
|
||||
<Button className="rb:h-6! rb:px-2! rb:leading-5.5!" type="primary" onClick={handleExtract}>+ {t('ontology.extract')}</Button>
|
||||
</Space>)}
|
||||
/>
|
||||
|
||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
||||
<Row gutter={16} className="rb:mb-4">
|
||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:pb-3 rb:px-3">
|
||||
<Row gutter={12} className="rb:mb-4">
|
||||
<Col span={6} offset={18}>
|
||||
<SearchInput
|
||||
placeholder={t('ontology.classSearchPlaceholder')}
|
||||
@@ -128,13 +139,12 @@ const Detail: FC = () => {
|
||||
<RbCard
|
||||
title={item.class_name}
|
||||
extra={data.is_system_default ? undefined : (<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/common/delete.svg')] rb:hover:bg-[url('@/assets/images/common/delete_hover.svg')]"
|
||||
onClick={() => handleDelete(item)}
|
||||
></div>)}
|
||||
className="rb:bg-transparent!"
|
||||
>
|
||||
<Tooltip title={item.class_description}>
|
||||
<div className="rb:h-8.5 rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:-mt-1 rb:wrap-break-word rb:line-clamp-2">{item.class_description}</div>
|
||||
<div className="rb:h-10 rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:wrap-break-word rb:line-clamp-2">{item.class_description}</div>
|
||||
</Tooltip>
|
||||
</RbCard>
|
||||
</Col>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-05 10:43:49
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 10:43:49
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-20 20:28:44
|
||||
*/
|
||||
import React, { useRef } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
@@ -10,9 +10,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { Skill } from './types'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getSkillListUrl } from '@/api/skill'
|
||||
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
|
||||
/**
|
||||
* Skills List Page Component
|
||||
@@ -66,14 +67,15 @@ const Skills: React.FC = () => {
|
||||
return (
|
||||
<RbCard
|
||||
title={item.name}
|
||||
avatar={<div className="rb:w-12 rb:h-12 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-12 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF] rb:mr-2">{item.name[0]}</div>}
|
||||
className="rb:cursor-pointer"
|
||||
titleClassName="rb:line-clamp-1!"
|
||||
onClick={() => handleView(item)}
|
||||
>
|
||||
{/* Skill description with tooltip */}
|
||||
<Tooltip title={item.description}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:-mt-1 rb:wrap-break-word rb:line-clamp-1">{item.description}</div>
|
||||
<div className="rb:h-10 rb:leading-5 rb:wrap-break-word rb:line-clamp-2">{item.description}</div>
|
||||
</Tooltip>
|
||||
<div className="rb:text-[#5B6167] rb:leading-4.5 rb:text-[12px] rb:mt-4">{t('common.updated_at')}: {formatDateTime(item.updated_at)}</div>
|
||||
</RbCard>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:48:59
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 17:48:59
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-20 18:49:51
|
||||
*/
|
||||
/**
|
||||
* Space Management Page
|
||||
@@ -11,13 +11,12 @@
|
||||
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { List, Button } from 'antd';
|
||||
import { List, Button, Flex, Space as AntSpace, Tooltip } from 'antd';
|
||||
|
||||
import type { Space, SpaceModalRef } from './types';
|
||||
import SpaceModal from './components/SpaceModal';
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getWorkspaces, switchWorkspace } from '@/api/workspaces'
|
||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||
import Tag from '@/components/Tag'
|
||||
@@ -76,20 +75,21 @@ const SpaceManagement: React.FC = () => {
|
||||
<List.Item key={item.id}>
|
||||
<RbCard
|
||||
avatarUrl={item.icon}
|
||||
avatar={<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>}
|
||||
title={item.name}
|
||||
subTitle={<Tag className="rb:mt-1 rb:font-regular!" color={item.storage_type === 'rag' ? 'processing' : 'warning'}>{t(`space.${item.storage_type || 'neo4j'}`)}</Tag>}
|
||||
>
|
||||
<div className={clsx("rb:absolute rb:-top-px rb:-right-px rb:p-[2px_9px] rb:text-[#FFFFFF] rb:leading-4 rb:text-[12px] rb:font-regular rb:rounded-[0px_12px_0px_12px]", {
|
||||
'rb:bg-[#369F21]': item.is_active,
|
||||
'rb:bg-[#A8A9AA]': !item.is_active,
|
||||
})}>{item.is_active ? t('space.associated') : t('space.notAssociated')}</div>
|
||||
|
||||
<Button type="primary" ghost block className="rb:mt-10" onClick={() => handleJump(item.id)}>
|
||||
avatarText={item.name[0]}
|
||||
title={<Flex vertical gap={6}>
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
<AntSpace>
|
||||
<Tag color={item.storage_type === 'rag' ? 'processing' : 'warning'}>{t(`space.${item.storage_type || 'neo4j'}`)}</Tag>
|
||||
<Tag color={item.is_active ? 'success' : 'error'}>{item.is_active ? t('space.associated') : t('space.notAssociated')}</Tag>
|
||||
</AntSpace>
|
||||
</Flex>}
|
||||
isNeedTooltip={false}
|
||||
footer={<Button type="primary" ghost block className="rb:mt-2 rb:h-9!" onClick={() => handleJump(item.id)}>
|
||||
{t('space.enterSpace')}
|
||||
</Button>
|
||||
</Button>}
|
||||
>
|
||||
</RbCard>
|
||||
</List.Item>
|
||||
)}
|
||||
|
||||
@@ -1,37 +1,40 @@
|
||||
import React, { useState, useRef, useEffect, type ReactNode } from 'react';
|
||||
import { useState, useRef, useEffect, forwardRef, useImperativeHandle, type ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
App,
|
||||
List,
|
||||
Space
|
||||
Space,
|
||||
Flex,
|
||||
Tooltip,
|
||||
Dropdown,
|
||||
} from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import type { ToolItem, Query, CustomToolModalRef } from './types';
|
||||
import type { ToolItem, CustomToolModalRef, CustomRef } from './types';
|
||||
import CustomToolModal from './components/CustomToolModal';
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getTools, deleteTool } from '@/api/tools'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
|
||||
const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
|
||||
const Custom = forwardRef<CustomRef, { getStatusTag: (status: string) => ReactNode; keyword?: string | undefined }>(({ getStatusTag, keyword }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message, modal } = App.useApp()
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<ToolItem[]>([]);
|
||||
const [query, setQuery] = useState<Query>({ name: undefined, tool_type: 'custom' });
|
||||
const customToolModalRef = useRef<CustomToolModalRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [query.name])
|
||||
}, [keyword])
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true)
|
||||
getTools(query)
|
||||
getTools({
|
||||
tool_type: 'custom',
|
||||
name: keyword
|
||||
})
|
||||
.then((res) => {
|
||||
setData(res as ToolItem[])
|
||||
})
|
||||
@@ -39,15 +42,14 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery(prev => ({ ...prev, name: value }))
|
||||
}
|
||||
|
||||
// 打开添加服务弹窗
|
||||
const handleEdit = (data?: ToolItem) => {
|
||||
customToolModalRef.current?.handleOpen(data);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({ handleEdit }));
|
||||
|
||||
// 删除服务
|
||||
const handleDeleteService = (item: ToolItem) => {
|
||||
modal.confirm({
|
||||
@@ -65,71 +67,80 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={16} className='rb:mb-4 rb:w-full'>
|
||||
<Col span={8}>
|
||||
<SearchInput
|
||||
placeholder={t('tool.customSearchPlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={16} className="rb:text-right">
|
||||
<Button type="primary" onClick={() => {handleEdit()}}>{t('tool.addCustom')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<>
|
||||
<BodyWrapper loading={loading} empty={data.length === 0}>
|
||||
<List
|
||||
grid={{ gutter: 16, column: 2 }}
|
||||
grid={{ gutter: 16, column: 3 }}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item key={item.id}>
|
||||
<RbCard
|
||||
// avatar={
|
||||
// <div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
// {item.name[0]}
|
||||
// </div>
|
||||
// }
|
||||
title={
|
||||
<div>
|
||||
{item.name}<br/>
|
||||
{/* <div className="rb:mt-1 rb:text-[12px] rb:leading-4 rb:font-regular rb:text-[#5B6167]">xx个工具</div> */}
|
||||
</div>
|
||||
}
|
||||
extra={getStatusTag(item.status)}
|
||||
>
|
||||
<div>
|
||||
{['auth_type', 'tags', 'created_at'].map(key => (
|
||||
<div
|
||||
key={key}
|
||||
className="rb:flex rb:gap-4 rb:justify-start rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<div className="rb:whitespace-nowrap rb:w-32">{t(`tool.${key}`)}</div>
|
||||
<div className='rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-inline rb:text-left rb:py-px rb:rounded rb:font-medium'>
|
||||
{key === 'created_at' && item[key]
|
||||
? dayjs(item[key]).format('YYYY-MM-DD HH:mm:ss')
|
||||
: key === 'auth_type'
|
||||
? t(`tool.${(item.config_data as any)?.[key]}`)
|
||||
: key === 'tags'
|
||||
? (item[key] as string[]).join('、')
|
||||
: (item.config_data as any)?.[key] || '-'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="rb:mt-4 rb:text-[12px] rb:leading-4 rb:font-regular rb:text-[#5B6167] rb:flex rb:items-center rb:justify-end">
|
||||
<Space size={16}>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
onClick={() => handleEdit(item)}
|
||||
></div>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={() => handleDeleteService(item)}
|
||||
></div>
|
||||
<Flex justify="space-between" gap={16}>
|
||||
<Space size={8} className="rb:flex-1!">
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
{getStatusTag(item.status)}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'edit',
|
||||
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit.svg')]" />,
|
||||
label: t('common.edit'),
|
||||
onClick: () => handleEdit(item),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
className: 'rb:text-[#FF5D34]!',
|
||||
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/delete_red.svg')]" />,
|
||||
label: t('common.delete'),
|
||||
onClick: () => handleDeleteService(item),
|
||||
},
|
||||
]
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="rb:cursor-pointer rb:size-6 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]"></div>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
}
|
||||
isNeedTooltip={false}
|
||||
>
|
||||
|
||||
{item.tags?.length > 0
|
||||
? <Flex gap={8} wrap align="center">
|
||||
<Flex gap={6}>
|
||||
{item.tags?.slice(0, 2).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">{type}</div>
|
||||
))}
|
||||
</Flex>
|
||||
{item.tags.length > 2 && (
|
||||
<Tooltip
|
||||
title={<Flex wrap gap={6}>{item.tags?.slice(2, item.tags.length).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5 rb:text-[#171719]">{type}</div>
|
||||
))}</Flex>}
|
||||
color="white"
|
||||
placement="bottom"
|
||||
>
|
||||
<div className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">+{item.tags.length - 2}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Flex>
|
||||
: <div className="rb:text-[#A8A9AA] rb:leading-5">{t('tool.noTags')}</div>
|
||||
}
|
||||
<Row className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2! rb:px-3! rb:leading-5 rb:mt-4!">
|
||||
<Col span={12}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('tool.auth_type')}</div>
|
||||
{(item.config_data as any)?.auth_type}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('tool.created_at')}</div>
|
||||
{formatDateTime(item.created_at)}
|
||||
</Col>
|
||||
</Row>
|
||||
</RbCard>
|
||||
</List.Item>
|
||||
)}
|
||||
@@ -142,8 +153,8 @@ const Custom: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ get
|
||||
ref={customToolModalRef}
|
||||
refresh={getData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Custom;
|
||||
@@ -1,30 +1,28 @@
|
||||
import React, { useState, useRef, useEffect, type ReactNode } from 'react';
|
||||
import {
|
||||
List,
|
||||
Flex,
|
||||
Space,
|
||||
Tooltip,
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
List,
|
||||
Flex
|
||||
} from 'antd';
|
||||
import { EyeOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import dayjs, { type Dayjs } from 'dayjs'
|
||||
|
||||
import type { Query, ToolItem, TimeToolModalRef, JsonToolModalRef, InnerToolModalRef } from './types';
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import type { ToolItem, TimeToolModalRef, JsonToolModalRef, InnerToolModalRef } from './types';
|
||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import TimeToolModal from './components/TimeToolModal'
|
||||
import JsonToolModal from './components/JsonToolModal'
|
||||
import InnerToolModal from './components/InnerToolModal'
|
||||
import { getTools } from '@/api/tools'
|
||||
import { InnerConfigData } from './constant'
|
||||
|
||||
const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
|
||||
const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode; keyword?: string | undefined }> = ({ getStatusTag, keyword }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<ToolItem[]>([]);
|
||||
const [query, setQuery] = useState<Query>({ name: undefined, tool_type: 'builtin' });
|
||||
const [curTime, setCurTime] = useState<Dayjs>(dayjs())
|
||||
const timeToolModalRef = useRef<TimeToolModalRef>(null)
|
||||
const jsonToolModalRef = useRef<JsonToolModalRef>(null)
|
||||
@@ -38,11 +36,14 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [query.name])
|
||||
}, [keyword])
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true)
|
||||
getTools(query)
|
||||
getTools({
|
||||
tool_type: 'builtin',
|
||||
name: keyword
|
||||
})
|
||||
.then((res) => {
|
||||
setData(res as ToolItem[])
|
||||
})
|
||||
@@ -50,9 +51,6 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery(prev => ({ ...prev, name: value }))
|
||||
}
|
||||
|
||||
// 打开添加服务弹窗
|
||||
const handleEdit = (data: ToolItem) => {
|
||||
@@ -71,78 +69,77 @@ const Inner: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getS
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={16} className='rb:mb-4 rb:w-full'>
|
||||
<Col span={8}>
|
||||
<SearchInput
|
||||
placeholder={t('tool.innerSearchPlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<BodyWrapper loading={loading} empty={data.length === 0}>
|
||||
<List
|
||||
grid={{ gutter: 16, column: 2 }}
|
||||
grid={{ gutter: 16, column: 3 }}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item key={item.id} className='rb:h-full!'>
|
||||
<RbCard
|
||||
// className={clsx({
|
||||
// 'rb:h-85.5!': item.config_data.tool_class === 'DateTimeTool' || item.config_data.tool_class === 'JsonTool'
|
||||
// })}
|
||||
// avatar={
|
||||
// <div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
// {item.name[0]}
|
||||
// </div>
|
||||
// }
|
||||
title={item.name}
|
||||
extra={getStatusTag(item.status)}
|
||||
bodyClassName='rb:h-[calc(100%-40px)]'
|
||||
>
|
||||
<div className="rb:h-full rb:flex rb:flex-col rb:justify-between">
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:font-regular rb:text-[#5B6167]">
|
||||
{t(`tool.${item.config_data.tool_class}_features`)} <br />
|
||||
<Flex gap={4} wrap className="rb:mt-2 rb:w-full">
|
||||
{InnerConfigData[item.config_data.tool_class].features.map(vo => <Tag key={vo} color="default">{ t(`tool.${vo}`) }</Tag>) }
|
||||
title={
|
||||
<Flex justify="space-between" gap={16}>
|
||||
<Space size={8}>
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
{getStatusTag(item.status)}
|
||||
</Space>
|
||||
<Flex align="center" justify="center" className="rb:size-5.5 rb:hover:bg-[#F6F6F6] rb:rounded-md">
|
||||
<div
|
||||
className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit.svg')]"
|
||||
onClick={() => handleEdit(item)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
isNeedTooltip={false}
|
||||
>
|
||||
<Tooltip title={t(`tool.${item.config_data.tool_class}_features`)}>
|
||||
<div className="rb:h-10 rb:wrap-break-word rb:line-clamp-2 rb:leading-5">{t(`tool.${item.config_data.tool_class}_features`)}</div>
|
||||
</Tooltip>
|
||||
|
||||
{item.config_data.tool_class === 'DateTimeTool'
|
||||
? <div className="rb:mt-3 rb:bg-[#F0F3F8] rb:px-3 rb:py-2.5 rb:rounded-md">
|
||||
{t('tool.currentTime')}
|
||||
<div className="rb:font-medium rb:bg-white rb:px-3 rb:py-2.5 rb:rounded-md rb:my-2">
|
||||
{curTime.format('YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
{t('tool.timestamp')}
|
||||
<div className="rb:font-medium rb:bg-white rb:px-3 rb:py-2.5 rb:rounded-md rb:mt-2">
|
||||
{curTime.unix()}
|
||||
</div>
|
||||
</div>
|
||||
:item.config_data.tool_class === 'JsonTool'
|
||||
? <div className="rb:mt-3 rb:bg-[#F0F3F8] rb:px-3 rb:py-2.5 rb:rounded-md">
|
||||
{t('tool.jsonEg')}
|
||||
<div className="rb:font-medium rb:bg-white rb:px-3 rb:py-2.5 rb:rounded-md rb:my-2">
|
||||
{InnerConfigData[item.config_data.tool_class].eg}
|
||||
</div>
|
||||
</div>
|
||||
: <div className="rb:mt-3 rb:bg-[#F0F3F8] rb:px-3 rb:py-2.5 rb:rounded-md">
|
||||
{t('tool.configStatus')}
|
||||
<div className="rb:font-medium rb:bg-white rb:px-3 rb:py-2.5 rb:rounded-md rb:my-2">
|
||||
{t(`tool.${item.status}_desc`)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Flex gap={8} wrap align="center" className="rb:mt-2! rb:mb-4!">
|
||||
<Flex gap={6}>
|
||||
{InnerConfigData[item.config_data.tool_class].features?.slice(0, 2).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">{type}</div>
|
||||
))}
|
||||
</Flex>
|
||||
{InnerConfigData[item.config_data.tool_class].features.length > 2 && (
|
||||
<Tooltip
|
||||
title={<Flex wrap gap={6}>{InnerConfigData[item.config_data.tool_class].features?.slice(2, InnerConfigData[item.config_data.tool_class].features.length).map((type, i) => (
|
||||
<div key={i} className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5 rb:text-[#171719]">{type}</div>
|
||||
))}</Flex>}
|
||||
color="white"
|
||||
placement="bottom"
|
||||
>
|
||||
<div className="rb:bg-[#F6F6F6] rb:rounded-md rb:py-px rb:px-1 rb:text-[12px] rb:leading-4.5">+{InnerConfigData[item.config_data.tool_class].features.length - 2}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<div className="rb:mt-4 rb:flex rb:items-center rb:justify-end">
|
||||
{item.config_data.tool_class === 'DateTimeTool' || item.config_data.tool_class === 'JsonTool' ?
|
||||
<EyeOutlined className="rb:text-5 rb:text-[#5B6167]! rb:hover:text-[#212332]!" onClick={() => handleEdit(item)} />
|
||||
: <div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
onClick={() => handleEdit(item)}
|
||||
></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Row className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2! rb:px-3! rb:leading-5">
|
||||
{item.config_data.tool_class === 'DateTimeTool'
|
||||
? <>
|
||||
<Col span={12}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('tool.currentTime')}</div>
|
||||
{curTime.format('YYYY-MM-DD HH:mm:ss')}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('tool.timestamp')}</div>
|
||||
{curTime.unix()}
|
||||
</Col>
|
||||
</>
|
||||
: item.config_data.tool_class === 'JsonTool'
|
||||
? <Col span={24}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('tool.jsonEg')}</div>
|
||||
{InnerConfigData[item.config_data.tool_class].eg}
|
||||
</Col>
|
||||
: <Col span={24}>
|
||||
<div className="rb:text-[#5B6167] rb:mb-1">{t('configStatus')}</div>
|
||||
{t(`tool.${item.status}_desc`)}
|
||||
</Col>
|
||||
}
|
||||
</Row>
|
||||
</RbCard>
|
||||
</List.Item>
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import React, { useState, useRef, useEffect, useCallback, type ReactNode } from 'react';
|
||||
import { Input, Button, App, Card, Space, Skeleton, Tag } from 'antd';
|
||||
import { SearchOutlined, SettingOutlined, GlobalOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { Button, App, Space, Row, Col, Flex, Tooltip } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||
import clsx from 'clsx'
|
||||
|
||||
import MarketConfigModal, { type MarketConfigModalRef } from './components/MarketConfigModal';
|
||||
import McpServiceModal from './components/McpServiceModal';
|
||||
import type { McpServiceModalRef } from './types';
|
||||
import pageEmptyIcon from '@/assets/images/empty/pageEmpty.png'
|
||||
import Empty from '@/components/Empty/index'
|
||||
import { getMarketTools, getMarketConfig, getMarketMCPs, getMarketMCPDetail, getMarketMCPsActivated, getTools } from '@/api/tools';
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
import RbCard from '@/components/RbCard'
|
||||
import Tag from '@/components/Tag'
|
||||
import marketIcon from '@/assets/images/tool/market.png'
|
||||
|
||||
interface MarketSource {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -97,6 +103,9 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
});
|
||||
|
||||
setCategories(Array.from(categoryMap.values()));
|
||||
if (response.items[0]?.id) {
|
||||
handleSelectSource(response.items[0]?.id)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取市场数据失败:', error);
|
||||
@@ -223,6 +232,7 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
};
|
||||
|
||||
const handleSelectSource = async (sourceId: string) => {
|
||||
if (sourceId === selectedSource) return
|
||||
setSelectedSource(sourceId);
|
||||
setSearchKeyword('');
|
||||
setCurrentPage(1);
|
||||
@@ -235,21 +245,6 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
await fetchMcpList(sourceId, 1);
|
||||
};
|
||||
|
||||
const handleRefresh = async (sourceId: string) => {
|
||||
// 清除缓存,重新从第一页加载
|
||||
setMcpCache(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[sourceId];
|
||||
return next;
|
||||
});
|
||||
setCurrentPage(1);
|
||||
await fetchMcpList(sourceId, 1);
|
||||
const source = marketSources.find(s => s.id === sourceId);
|
||||
if (source) {
|
||||
message.success(`${source.name} ${t('tool.marketRefreshSuccess')}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenConfig = async (sourceId: string) => {
|
||||
const source = marketSources.find(s => s.id === sourceId);
|
||||
if (!source) return;
|
||||
@@ -329,13 +324,13 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
if (!selectedSource) {
|
||||
return (
|
||||
<div className="rb:flex rb:flex-col rb:items-center rb:justify-center rb:h-full rb:text-center">
|
||||
<Empty
|
||||
url={pageEmptyIcon}
|
||||
title={t('tool.marketSelectTitle')}
|
||||
subTitle={t('tool.marketSelectDesc')}
|
||||
size={200}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
<Empty
|
||||
url={pageEmptyIcon}
|
||||
title={t('tool.marketSelectTitle')}
|
||||
subTitle={t('tool.marketSelectDesc')}
|
||||
size={200}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
@@ -348,230 +343,170 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rb:flex rb:justify-between rb:items-center rb:pb-0">
|
||||
<div className="rb:flex rb:items-center rb:gap-4">
|
||||
<div className="rb:w-10 rb:h-10 rb:flex rb:items-center rb:justify-center rb:bg-gray-50 rb:rounded-xl rb:flex-shrink-0 rb:overflow-hidden">
|
||||
<Flex justify="space-between" align="center">
|
||||
<Flex gap={12} align="center" className="rb:pl-1!">
|
||||
<Flex align="center" justify="center" className="rb:size-12">
|
||||
{source.logo_url ? (
|
||||
<img
|
||||
src={source.logo_url}
|
||||
alt={source.name}
|
||||
className="rb:w-full rb:h-full rb:object-cover"
|
||||
<img
|
||||
src={source.logo_url}
|
||||
alt={source.name}
|
||||
className="rb:w-full rb:h-full rb:object-cover rb:rounded-xl"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const parent = e.currentTarget.parentElement;
|
||||
if (parent) {
|
||||
parent.innerHTML = '🏪';
|
||||
parent.style.fontSize = '48px';
|
||||
}
|
||||
e.currentTarget.src = marketIcon
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="rb:text-5xl">🏪</span>
|
||||
<div className="rb:size-12 rb:rounded-xl rb:bg-cover rb:bg-[url('@/assets/images/tool/market.png')]"></div>
|
||||
)}
|
||||
</Flex>
|
||||
<div>
|
||||
<div className="rb:font-[MiSans Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{source.name}</div>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{t('tool.availableMcp')} ({mcpTotal})</div>
|
||||
</div>
|
||||
<div className="rb:flex rb:items-center rb:flex-1">
|
||||
<h2 className="rb:text-xl rb:font-semibold rb:text-gray-900 rb:mb-2 rb:mr-2">{source.name}</h2>
|
||||
可用 MCP 服务 <span className="rb:text-gray-600 rb:font-normal">({mcpTotal})</span>
|
||||
{/* <p className="rb:text-sm rb:text-gray-600 rb:leading-relaxed">{source.description}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<div className="rb:flex rb:gap-3">
|
||||
<div className="rb:flex rb:gap-3 rb:items-center">
|
||||
{source.connected && (
|
||||
<Button size="small" icon={<SyncOutlined />} onClick={() => handleRefresh(selectedSource)}>
|
||||
{t('tool.marketRefresh')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Input
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('tool.marketSearchPlaceholder')}
|
||||
value={searchKeyword}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
<Button icon={<SettingOutlined />} onClick={() => handleOpenConfig(selectedSource)}>
|
||||
<Space size={12}>
|
||||
<SearchInput
|
||||
placeholder={t('tool.marketSearchPlaceholder')}
|
||||
value={searchKeyword}
|
||||
onSearch={(value: string) => handleSearchChange(value)}
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Button type="primary" ghost onClick={() => handleOpenConfig(selectedSource)}>
|
||||
{t('tool.marketConfigBtn')}
|
||||
</Button>
|
||||
<Button type="primary" icon={<GlobalOutlined />} onClick={() => window.open(source.url, '_blank')}>
|
||||
<Button type="primary" onClick={() => window.open(source.url, '_blank')}>
|
||||
{t('tool.marketVisit')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<div className="rb:mt-6">
|
||||
<div id="mcpScrollableDiv" className="rb:overflow-y-auto rb:h-[calc(100vh-260px)]">
|
||||
{!loading && mcpList.length === 0 ? (
|
||||
<Empty
|
||||
url={pageEmptyIcon}
|
||||
title={searchKeyword ? t('tool.marketNoSearchResult') : t('tool.marketNoData')}
|
||||
subTitle={searchKeyword ? t('tool.marketNoSearchResultDesc') : t('tool.marketNoDataDesc')}
|
||||
size={200}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
dataLength={mcpList.length}
|
||||
next={loadMore}
|
||||
hasMore={hasMore}
|
||||
loader={null}
|
||||
scrollableTarget="mcpScrollableDiv"
|
||||
>
|
||||
<div
|
||||
className="rb:gap-4"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
||||
}}
|
||||
>
|
||||
{mcpList.map(mcp => (
|
||||
<div
|
||||
key={mcp.id}
|
||||
className="rb:bg-white rb:border rb:border-gray-200 rb:rounded-lg rb:p-4 rb:pb-2 rb:transition-all rb:duration-200 hover:rb:shadow-lg hover:rb:border-gray-300"
|
||||
<div className="rb:mt-4">
|
||||
<div id="mcpScrollableDiv" className="rb:overflow-y-auto rb:h-[calc(100vh-188px)]">
|
||||
{!loading && mcpList.length === 0 ? (
|
||||
<Empty
|
||||
url={pageEmptyIcon}
|
||||
title={searchKeyword ? t('tool.marketNoSearchResult') : t('tool.marketNoData')}
|
||||
subTitle={searchKeyword ? t('tool.marketNoSearchResultDesc') : t('tool.marketNoDataDesc')}
|
||||
size={200}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
dataLength={mcpList.length}
|
||||
next={loadMore}
|
||||
hasMore={hasMore}
|
||||
loader={null}
|
||||
scrollableTarget="mcpScrollableDiv"
|
||||
>
|
||||
<Row gutter={[12,12]}>
|
||||
{mcpList.map(mcp => (
|
||||
<Col
|
||||
key={mcp.id}
|
||||
span={12}
|
||||
>
|
||||
<div className="rb:flex rb:justify-between rb:items-center rb:mb-3">
|
||||
<div className="rb:w-12 rb:h-12 rb:flex rb:items-center rb:justify-center rb:bg-gray-50 rb:rounded-lg rb:overflow-hidden">
|
||||
{mcp.logo_url ? (
|
||||
<img
|
||||
src={mcp.logo_url}
|
||||
alt={getLocaleField(mcp, 'name')}
|
||||
className="rb:w-full rb:h-full rb:object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const parent = e.currentTarget.parentElement;
|
||||
if (parent) {
|
||||
parent.innerHTML = '🔧';
|
||||
parent.style.fontSize = '24px';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="rb:text-3xl">🔧</span>
|
||||
)}
|
||||
</div>
|
||||
{mcp.categories?.[0] && (
|
||||
<span className="rb:px-2 rb:py-1 rb:rounded rb:text-xs rb:font-medium rb:bg-blue-50 rb:text-blue-700">
|
||||
{mcp.categories[0]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="rb:text-base rb:font-semibold rb:text-gray-900 rb:mb-1">{getLocaleField(mcp, 'name')}</h3>
|
||||
{mcp.publisher && (
|
||||
<div className="rb:mb-2">
|
||||
<span className="rb:text-xs rb:text-gray-500">{mcp.publisher.startsWith('@') ? mcp.publisher : `@${mcp.publisher}`}</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="rb:text-sm rb:text-gray-600 rb:line-clamp-2 rb:mb-3 rb:min-h-10">{getLocaleField(mcp, 'description')}</p>
|
||||
<div className="rb:flex rb:gap-4 rb:mb-3 rb:pt-3 rb:border-t rb:border-gray-100">
|
||||
{mcp.view_count != null && (
|
||||
<span className="rb:flex rb:items-center rb:gap-1 rb:text-xs rb:text-gray-500">
|
||||
<GlobalOutlined /> {mcp.view_count.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`rb:flex rb:items-center ${mcp.activated || mcp.inDatabase ? 'rb:justify-between' : 'rb:justify-end'}`}>
|
||||
<div className="rb:flex rb:gap-2">
|
||||
{mcp.activated && <Tag color="success">{t('tool.marketActivated')}</Tag>}
|
||||
{mcp.inDatabase && <Tag color="blue">{t('tool.marketInDatabase')}</Tag>}
|
||||
</div>
|
||||
<Button disabled={mcp.inDatabase} type="primary" size="small" onClick={() => handleOpenMcpServiceModal(mcp)}>
|
||||
+ {t('tool.marketAdd')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<RbCard
|
||||
avatarUrl={mcp.logo_url || marketIcon}
|
||||
title={
|
||||
<Flex justify="space-between" gap={16}>
|
||||
<Flex vertical gap={6}>
|
||||
<Tooltip title={getLocaleField(mcp, 'name')}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{getLocaleField(mcp, 'name')}</div>
|
||||
</Tooltip>
|
||||
<Flex gap={8} wrap className='rb:wrap-break-word rb:line-clamp-1'>
|
||||
{mcp.categories?.[0] && (
|
||||
<Tag>{mcp.categories[0]}</Tag>
|
||||
)}
|
||||
{mcp.activated && <Tag color="success">{t('tool.marketActivated')}</Tag>}
|
||||
{mcp.inDatabase && <Tag>{t('tool.marketInDatabase')}</Tag>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
disabled={mcp.inDatabase}
|
||||
size="small"
|
||||
onClick={() => handleOpenMcpServiceModal(mcp)}
|
||||
>+</Button>
|
||||
</Flex>
|
||||
}
|
||||
isNeedTooltip={false}
|
||||
footer={<Flex justify="space-between" align="center" className="rb:text-[#5B6167] rb:text-[12px] rb:mb-1!">
|
||||
{mcp.publisher && <span>{mcp.publisher.startsWith('@') ? mcp.publisher : `@${mcp.publisher}`}</span>}
|
||||
{mcp.view_count && <Space size={4}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/global_outline.svg')]"></div>
|
||||
{mcp.view_count.toLocaleString()}
|
||||
</Space>}
|
||||
</Flex>}
|
||||
>
|
||||
{getLocaleField(mcp, 'description') ?
|
||||
<Tooltip title={getLocaleField(mcp, 'description')}>
|
||||
<div className="rb:h-10 rb:leading-5 rb:wrap-break-word rb:line-clamp-2 rb:mt-2">{getLocaleField(mcp, 'description')}</div>
|
||||
</Tooltip>
|
||||
: <div className="rb:h-10 rb:leading-5 rb:text-[#A8A9AA] rb:mt-2">{t('tool.descEmpty')}</div>
|
||||
}
|
||||
</RbCard>
|
||||
</Col>
|
||||
))}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
)}
|
||||
</div>
|
||||
</Row>
|
||||
</InfiniteScroll>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rb:flex rb:gap-4 rb:h-[calc(100vh-138px)]">
|
||||
{/* 左侧市场源列表 */}
|
||||
<div className="rb:w-80 rb:h-full rb:overflow-y-auto">
|
||||
<Space size={12} direction="vertical" className="rb:w-full">
|
||||
<Row gutter={16}>
|
||||
<Col flex="380px">
|
||||
<Flex vertical gap={16}>
|
||||
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] rb:leading-5.5">{t('tool.mcpMarket')}</div>
|
||||
{categories.map(cat => (
|
||||
<Card
|
||||
key={cat.id}
|
||||
type="inner"
|
||||
title={
|
||||
<div className="rb:flex rb:items-center rb:gap-2">
|
||||
<span>{cat.name}</span>
|
||||
</div>
|
||||
}
|
||||
classNames={{
|
||||
body: "rb:p-[10px]!",
|
||||
header: "rb:bg-[#F6F8FC]!"
|
||||
}}
|
||||
>
|
||||
<Space size={8} direction="vertical" className="rb:w-full">
|
||||
{marketSources
|
||||
.filter(s => s.category === cat.id)
|
||||
.map(source => (
|
||||
<div
|
||||
key={source.id}
|
||||
className={`rb:bg-white rb:rounded-lg rb:p-2 rb:border rb:cursor-pointer rb:flex rb:items-center rb:gap-2 rb:transition-all ${
|
||||
selectedSource === source.id
|
||||
? 'rb:border-[#155EEF] rb:shadow-[0px_2px_4px_0px_rgba(33,35,50,0.15)]'
|
||||
: 'rb:border-[#DFE4ED] rb:hover:border-[#155EEF] rb:hover:shadow-[0px_2px_4px_0px_rgba(33,35,50,0.15)]'
|
||||
}`}
|
||||
onClick={() => handleSelectSource(source.id)}
|
||||
>
|
||||
<div className="rb:w-5 rb:h-5 rb:flex-shrink-0 rb:flex rb:items-center rb:justify-center rb:overflow-hidden rb:rounded rb:bg-gray-100">
|
||||
{source.logo_url ? (
|
||||
<img
|
||||
src={source.logo_url}
|
||||
alt={source.name}
|
||||
className="rb:w-full rb:h-full rb:object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const parent = e.currentTarget.parentElement;
|
||||
if (parent) {
|
||||
parent.innerHTML = '🏪';
|
||||
parent.style.fontSize = '16px';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="rb:text-base">🏪</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="rb:flex-1 rb:font-medium rb:text-[12px] rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
|
||||
{source.name}
|
||||
</span>
|
||||
{/* <span className="rb:text-xs rb:text-gray-500 rb:px-1.5 rb:py-0.5 rb:bg-gray-100 rb:rounded-full rb:flex-shrink-0">
|
||||
{source.mcp_count}
|
||||
</span> */}
|
||||
{source.connected && (
|
||||
<span className="rb:text-green-500 rb:text-[8px] rb:flex-shrink-0">●</span>
|
||||
<Flex key={cat.id} vertical gap={8}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:font-medium rb:leading-4.5">
|
||||
{cat.name}
|
||||
</div>
|
||||
{marketSources
|
||||
.filter(s => s.category === cat.id)
|
||||
.map(source => (
|
||||
<Flex
|
||||
key={source.id}
|
||||
align="center"
|
||||
gap={8}
|
||||
className={clsx('rb:bg-white rb:rounded-xl rb:py-2! rb:px-3! rb:cursor-pointer rb:transition-all', {
|
||||
'rb:border rb:border-[#171719]': selectedSource === source.id,
|
||||
'rb:shadow-[0px_2px_6px_0px_rgba(23,23,25,0.1)]': selectedSource !== source.id
|
||||
})}
|
||||
onClick={() => handleSelectSource(source.id)}
|
||||
>
|
||||
<div className="rb:size-7 rb:shrink-0 rb:flex rb:items-center rb:justify-center rb:overflow-hidden rb:rounded rb:bg-gray-100">
|
||||
{source.logo_url ? (
|
||||
<img
|
||||
src={source.logo_url}
|
||||
alt={source.name}
|
||||
className="rb:w-full rb:h-full rb:object-cover rb:rounded-sm"
|
||||
referrerPolicy="no-referrer"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = marketIcon;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="rb:size-7 rb:rounded-sm rb:bg-cover rb:bg-[url('@/assets/images/tool/market.png')]"></div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
<span className="rb:flex-1 rb:font-medium rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
|
||||
{source.name}
|
||||
</span>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 右侧内容区 */}
|
||||
<div className="rb:flex-1 rb:border-l rb:border-gray-200 rb:overflow-hidden">
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:p-6">
|
||||
{renderSourceDetail()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Flex>
|
||||
</Col>
|
||||
<Col flex="1">
|
||||
{renderSourceDetail()}
|
||||
</Col>
|
||||
{/* 配置弹窗 */}
|
||||
<MarketConfigModal
|
||||
ref={marketConfigModalRef}
|
||||
@@ -581,7 +516,7 @@ const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () =>
|
||||
ref={mcpServiceModalRef}
|
||||
refresh={handleRefreshAfterAdd}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import React, { useState, useRef, useEffect, type ReactNode } from 'react';
|
||||
import { useState, useRef, useEffect, forwardRef, useImperativeHandle, type ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
App,
|
||||
List,
|
||||
Space,
|
||||
Tooltip,
|
||||
Dropdown,
|
||||
Flex,
|
||||
} from 'antd';
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ToolItem, Query, McpServiceModalRef } from './types';
|
||||
import type { ToolItem, McpServiceModalRef, McpRef } from './types';
|
||||
import McpServiceModal from './components/McpServiceModal';
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbCard from '@/components/RbCard'
|
||||
import { getTools, deleteTool, testConnection } from '@/api/tools'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
|
||||
const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getStatusTag }) => {
|
||||
const Mcp = forwardRef<McpRef, { getStatusTag: (status: string) => ReactNode; keyword?: string | undefined }>(({ getStatusTag, keyword }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message, modal } = App.useApp()
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<ToolItem[]>([]);
|
||||
const [query, setQuery] = useState<Query>({ name: undefined, tool_type: 'mcp' });
|
||||
const addServiceModalRef = useRef<McpServiceModalRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [query.name])
|
||||
}, [keyword])
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true)
|
||||
getTools(query)
|
||||
getTools({
|
||||
tool_type: 'mcp',
|
||||
name: keyword
|
||||
})
|
||||
.then((res) => {
|
||||
setData(res as ToolItem[])
|
||||
})
|
||||
@@ -39,9 +40,8 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery(prev => ({ ...prev, name: value }))
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({ handleEdit, getData }));
|
||||
|
||||
// 打开添加服务弹窗
|
||||
const handleEdit = (data?: ToolItem) => {
|
||||
@@ -82,19 +82,7 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={16} className='rb:mb-4 rb:w-full'>
|
||||
<Col span={8}>
|
||||
<SearchInput
|
||||
placeholder={t('tool.mcpSearchPlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={16} className="rb:text-right">
|
||||
<Button type="primary" onClick={() => {handleEdit()}}>{t('tool.addService')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<>
|
||||
<BodyWrapper loading={loading} empty={data?.length === 0}>
|
||||
<List
|
||||
grid={{ gutter: 16, column: 3 }}
|
||||
@@ -102,58 +90,58 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
|
||||
renderItem={(item) => (
|
||||
<List.Item key={item.id}>
|
||||
<RbCard
|
||||
// avatar={
|
||||
// <div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
// {item.name[0]}
|
||||
// </div>
|
||||
// }
|
||||
title={item.name}
|
||||
extra={getStatusTag(item.status)}
|
||||
>
|
||||
<div>
|
||||
{[
|
||||
'server_url',
|
||||
'last_health_check',
|
||||
].map(key => {
|
||||
const value = item.config_data?.[key as keyof typeof item.config_data];
|
||||
let displayValue: React.ReactNode;
|
||||
|
||||
if (key === 'last_health_check') {
|
||||
displayValue = value ? new Date(value as number).toLocaleString() : '-';
|
||||
} else if (typeof value === 'string' || typeof value === 'number') {
|
||||
displayValue = value;
|
||||
} else {
|
||||
displayValue = '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="rb:flex rb:gap-4 rb:justify-start rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:mb-3"
|
||||
>
|
||||
<div className="rb:whitespace-nowrap rb:w-27.5">{t(`tool.${key}`)}</div>
|
||||
<div className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1">{displayValue}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="rb:mt-4 rb:text-[12px] rb:leading-4 rb:font-regular rb:text-[#5B6167] rb:flex rb:items-center rb:justify-end">
|
||||
<Space size={16}>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
onClick={() => handleEdit(item)}
|
||||
></div>
|
||||
<Button type="text" icon={<LinkOutlined />} onClick={() => handleTestConnection(item)}></Button>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={() => handleDeleteService(item)}
|
||||
></div>
|
||||
title={
|
||||
<Flex justify="space-between" gap={16}>
|
||||
<Space size={8} className="rb:flex-1!">
|
||||
<Tooltip title={item.name}>
|
||||
<div className="rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
</Tooltip>
|
||||
{getStatusTag(item.status)}
|
||||
</Space>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'edit',
|
||||
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit.svg')]" />,
|
||||
label: t('common.edit'),
|
||||
onClick: () => handleEdit(item),
|
||||
},
|
||||
{
|
||||
key: 'link',
|
||||
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/conversation/link.svg')]" />,
|
||||
label: t('tool.testLink'),
|
||||
onClick: () => handleTestConnection(item),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
className: 'rb:text-[#FF5D34]!',
|
||||
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/delete_red.svg')]" />,
|
||||
label: t('common.delete'),
|
||||
onClick: () => handleDeleteService(item),
|
||||
},
|
||||
]
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="rb:cursor-pointer rb:size-6 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]"></div>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
}
|
||||
isNeedTooltip={false}
|
||||
>
|
||||
<Flex vertical gap={4} className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2! rb:px-3! rb:text-[#5B6167] rb:leading-5">
|
||||
{t(`tool.server_url`)}
|
||||
<div className="rb:h-10 rb:break-all rb:line-clamp-2 rb:text-[#171719]">
|
||||
{item.config_data?.server_url}
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
<div className="rb:text-[#5B6167] rb:leading-4.5 rb:text-[12px] rb:mt-4">{t('tool.last_health_check')}: {formatDateTime(item.config_data?.last_health_check)}</div>
|
||||
|
||||
</RbCard>
|
||||
</List.Item>
|
||||
)}
|
||||
className="rb:h-[calc(100vh-178px)] rb:overflow-y-auto rb:overflow-x-hidden"
|
||||
className="rb:h-[calc(100vh-124px)] rb:overflow-y-auto rb:overflow-x-hidden"
|
||||
/>
|
||||
</BodyWrapper>
|
||||
|
||||
@@ -162,8 +150,8 @@ const Mcp: React.FC<{ getStatusTag: (status: string) => ReactNode }> = ({ getSta
|
||||
ref={addServiceModalRef}
|
||||
refresh={getData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Mcp;
|
||||
@@ -94,7 +94,7 @@ const InnerToolModal = forwardRef<InnerToolModalRef, InnerToolModalProps>(({
|
||||
confirmLoading={loading}
|
||||
>
|
||||
{editVo?.config_data?.tool_class && config && <>
|
||||
<RbAlert className="rb:mb-3">
|
||||
<RbAlert className="rb:mb-3!">
|
||||
<div>
|
||||
<div className="rb:text-[14px] rb:font-medium">{t('tool.configDesc')}</div>
|
||||
<div className="rb:mt-2">{t(`tool.${editVo?.config_data?.tool_class}_config_desc`)}</div>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* @LastEditors: yujiangping
|
||||
* @LastEditTime: 2026-03-06 15:11:31
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { type SegmentedProps, Flex, Space, Form, Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Mcp from './Mcp';
|
||||
@@ -15,20 +15,28 @@ import Inner from './Inner';
|
||||
import Custom from './Custom';
|
||||
import Market from './Market';
|
||||
import Tag from '@/components/Tag'
|
||||
import PageTabs from '@/components/PageTabs'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import type { McpRef, CustomRef } from './types'
|
||||
|
||||
const tabKeys = ['mcp', 'inner', 'custom', 'market'] //
|
||||
const ToolManagement: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState('mcp');
|
||||
const [activeTab, setActiveTab] = useState<SegmentedProps['value']>('mcp');
|
||||
const mcpRef = useRef<McpRef>(null);
|
||||
const customRef = useRef<CustomRef>(null);
|
||||
const [form] = Form.useForm();
|
||||
const name = Form.useWatch(['name'], form)
|
||||
|
||||
const formatTabItems = () => {
|
||||
return tabKeys.map(key => ({
|
||||
key,
|
||||
label: t(`tool.${key}`),
|
||||
return tabKeys.map(value => ({
|
||||
value,
|
||||
label: t(`tool.${value}`),
|
||||
}))
|
||||
}
|
||||
const handleChangeTab = (key: string) => {
|
||||
const handleChangeTab = (key: SegmentedProps['value']) => {
|
||||
setActiveTab(key);
|
||||
form.resetFields()
|
||||
}
|
||||
// 获取状态标签
|
||||
const getStatusTag = (status: string) => {
|
||||
@@ -45,17 +53,36 @@ const ToolManagement: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rb:-mt-4">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
items={formatTabItems()}
|
||||
onChange={handleChangeTab}
|
||||
/>
|
||||
{activeTab === 'mcp' && <Mcp getStatusTag={getStatusTag} />}
|
||||
{activeTab === 'inner' && <Inner getStatusTag={getStatusTag} />}
|
||||
{activeTab === 'custom' && <Custom getStatusTag={getStatusTag} />}
|
||||
<>
|
||||
<Flex justify="space-between" className="rb:mb-4!">
|
||||
<PageTabs
|
||||
value={activeTab}
|
||||
options={formatTabItems()}
|
||||
onChange={handleChangeTab}
|
||||
/>
|
||||
|
||||
{activeTab !== 'market' && <Form form={form}>
|
||||
<Space size={12}>
|
||||
<Form.Item name="name" noStyle>
|
||||
<SearchInput
|
||||
placeholder={t(`tool.${activeTab === 'mcp'
|
||||
? 'mcpSearchPlaceholder'
|
||||
: activeTab === 'custom'
|
||||
? 'customSearchPlaceholder'
|
||||
: 'innerSearchPlaceholder'
|
||||
}`)}
|
||||
/>
|
||||
</Form.Item>
|
||||
{activeTab === 'mcp' && <Button type="primary" onClick={() => mcpRef.current?.handleEdit()}>{t('tool.addService')}</Button>}
|
||||
{activeTab === 'custom' && <Button type="primary" onClick={() => customRef.current?.handleEdit()}>{t('tool.addCustom')}</Button>}
|
||||
</Space>
|
||||
</Form>}
|
||||
</Flex>
|
||||
{activeTab === 'mcp' && <Mcp ref={mcpRef} keyword={name} getStatusTag={getStatusTag} />}
|
||||
{activeTab === 'inner' && <Inner keyword={name} getStatusTag={getStatusTag} />}
|
||||
{activeTab === 'custom' && <Custom ref={customRef} keyword={name} getStatusTag={getStatusTag} />}
|
||||
{activeTab === 'market' && <Market getStatusTag={getStatusTag} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -147,4 +147,11 @@ export interface MarketQuery {
|
||||
page?: number;
|
||||
pagesize?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
export interface McpRef {
|
||||
handleEdit: (data?: ToolItem) => void;
|
||||
getData: () => void;
|
||||
}
|
||||
export interface CustomRef {
|
||||
handleEdit: (data?: ToolItem) => void;
|
||||
}
|
||||