Procházet zdrojové kódy

修复 MCP 与应用板块供应商标识显示

Samien před 1 měsícem
rodič
revize
b2a0016162

+ 4 - 0
THIRD_PARTY_NOTICES.md

@@ -22,3 +22,7 @@ Avatar silhouette paths and original two-eye expressions from zhulin025/LaoA-Gro
 ## ssh2 credential execution
 ## ssh2 credential execution
 
 
 `ssh2` 1.17.0 provides SSH public-key authentication, host key verification and SFTP for the protected credential executor. Copyright Brian White. MIT license: see `licenses/ssh2/LICENSE`. Source: https://github.com/mscdex/ssh2. Installation lifecycle scripts are not required for the pure JavaScript fallback.
 `ssh2` 1.17.0 provides SSH public-key authentication, host key verification and SFTP for the protected credential executor. Copyright Brian White. MIT license: see `licenses/ssh2/LICENSE`. Source: https://github.com/mscdex/ssh2. Installation lifecycle scripts are not required for the pure JavaScript fallback.
+
+## Application supplier logos
+
+`public/provider-logos/*.svg` contains the Gmail, Google Drive, Google Calendar, GitHub, Notion, Slack, Linear, Discord, Outlook, Trello, Airtable and X logos retrieved from `https://logos.composio.dev/api/{slug}` on 2026-10-02. These assets identify the corresponding integrations. Each brand's logo and trademark rights belong to its respective owner.

+ 13 - 0
public/provider-logos/airtable.svg

@@ -0,0 +1,13 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_0_309)">
+<path d="M57.1183 11.8136L9.51971 31.5412C6.88172 32.6882 6.88172 36.3584 9.51971 37.5054L57.3477 56.5448C61.5914 58.1505 66.1792 58.1505 70.4229 56.5448L118.366 37.5054C121.004 36.3584 121.118 32.6882 118.366 31.5412L70.767 11.8136C66.4086 9.97849 61.4767 9.97849 57.1183 11.8136Z" fill="#FCB400"/>
+<path d="M68.2437 66.8674V114.351C68.2437 116.645 70.5376 118.251 72.6022 117.333L126.05 96.6882C127.312 96.2294 128 95.0824 128 93.7061V46.2222C128 43.9283 125.706 42.3226 123.642 43.2401L70.1936 63.8853C68.9319 64.3441 68.2437 65.491 68.2437 66.8674Z" fill="#18BFFF"/>
+<path d="M0 46.3369V90.9534C0 93.362 2.75269 94.853 4.8172 93.8208L38.1936 77.7634L39.7993 76.9606L1.03226 44.1577C0.458781 44.7312 0 45.4193 0 46.3369Z" fill="#F82B60"/>
+<path d="M55.5125 63.5412L4.7025 43.3548C3.67024 43.0108 2.63799 43.1254 1.83512 43.5842C1.60573 43.6989 1.26164 43.9283 0.917557 44.1577L39.7993 76.9606L55.6272 69.3907C58.2652 68.129 58.0358 64.4588 55.5125 63.5412Z" fill="#BA1E45"/>
+</g>
+<defs>
+<clipPath id="clip0_0_309">
+<rect width="128" height="107.125" fill="white" transform="translate(0 10.4373)"/>
+</clipPath>
+</defs>
+</svg>

+ 15 - 0
public/provider-logos/discord.svg

@@ -0,0 +1,15 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_0_445)">
+<mask id="mask0_0_445" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="14" width="128" height="100">
+<path d="M128 14.4225H0V113.577H128V14.4225Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_0_445)">
+<path d="M108.357 23.2524C100.199 19.509 91.4506 16.7511 82.3035 15.1715C82.1369 15.141 81.9705 15.2172 81.8847 15.3695C80.7595 17.3707 79.5132 19.9813 78.6405 22.0333C68.8022 20.5604 59.0143 20.5604 49.3777 22.0333C48.5048 19.9357 47.2133 17.3707 46.0831 15.3695C45.9972 15.2223 45.8308 15.1461 45.6643 15.1715C36.5222 16.746 27.7737 19.504 19.6103 23.2524C19.5396 23.2828 19.4791 23.3337 19.4389 23.3997C2.84476 48.1909 -1.70107 72.3728 0.528961 96.2549C0.539051 96.3717 0.604639 96.4835 0.695456 96.5546C11.6438 104.595 22.2491 109.476 32.6575 112.711C32.8241 112.762 33.0006 112.701 33.1066 112.564C35.5687 109.202 37.7634 105.656 39.6452 101.928C39.7563 101.71 39.6503 101.451 39.4233 101.364C35.942 100.044 32.6272 98.4338 29.4386 96.6054C29.1863 96.4581 29.1662 96.0974 29.3982 95.9246C30.0692 95.4218 30.7404 94.8987 31.3811 94.3704C31.497 94.274 31.6585 94.2536 31.7948 94.3146C52.7429 103.879 75.4216 103.879 96.1224 94.3146C96.2587 94.2486 96.4202 94.2689 96.5412 94.3654C97.1821 94.8936 97.8531 95.4218 98.5292 95.9246C98.7612 96.0974 98.746 96.4581 98.4938 96.6054C95.3052 98.4693 91.9903 100.044 88.5041 101.359C88.2771 101.446 88.1761 101.71 88.2872 101.928C90.2094 105.651 92.4041 109.196 94.8208 112.559C94.9217 112.701 95.1033 112.762 95.2699 112.711C105.729 109.476 116.334 104.595 127.282 96.5546C127.378 96.4835 127.439 96.3768 127.449 96.26C130.118 68.6497 122.979 44.6661 108.524 23.4047C108.488 23.3337 108.428 23.2828 108.357 23.2524ZM42.7735 81.7132C36.4667 81.7132 31.27 75.9231 31.27 68.8123C31.27 61.7014 36.3659 55.9113 42.7735 55.9113C49.2313 55.9113 54.3776 61.7523 54.2767 68.8123C54.2767 75.9231 49.1808 81.7132 42.7735 81.7132ZM85.3053 81.7132C78.9987 81.7132 73.8021 75.9231 73.8021 68.8123C73.8021 61.7014 78.8978 55.9113 85.3053 55.9113C91.7634 55.9113 96.9095 61.7523 96.8087 68.8123C96.8087 75.9231 91.7634 81.7132 85.3053 81.7132Z" fill="#5865F2"/>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_0_445">
+<rect width="128" height="99.1549" fill="white" transform="translate(0 14.4225)"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
public/provider-logos/github.svg

@@ -0,0 +1,10 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_0_48)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M63.8093 1.30612C28.5244 1.30612 0 30.0408 0 65.5895C0 94.0055 18.2766 118.059 43.631 126.572C46.801 127.212 47.9621 125.189 47.9621 123.487C47.9621 121.997 47.8576 116.889 47.8576 111.566C30.1074 115.399 26.4111 103.903 26.4111 103.903C23.5585 96.4532 19.3319 94.5384 19.3319 94.5384C13.5223 90.6005 19.7551 90.6005 19.7551 90.6005C26.1995 91.0263 29.5811 97.199 29.5811 97.199C35.2849 106.99 44.4761 104.223 48.1737 102.52C48.7014 98.3693 50.3928 95.4958 52.1887 93.8997C38.0317 92.4095 23.1367 86.8754 23.1367 62.1832C23.1367 55.1589 25.6705 49.4119 29.6856 44.9424C29.0521 43.3463 26.833 36.7464 30.3203 27.9131C30.3203 27.9131 35.7081 26.21 47.8563 34.5117C53.0574 33.1045 58.4212 32.3887 63.8093 32.3827C69.1971 32.3827 74.6893 33.1285 79.761 34.5117C91.9105 26.21 97.2983 27.9131 97.2983 27.9131C100.786 36.7464 98.5652 43.3463 97.9318 44.9424C102.053 49.4119 104.482 55.1589 104.482 62.1832C104.482 86.8754 89.5869 92.3024 75.3241 93.8997C77.649 95.9216 79.6552 99.7525 79.6552 105.819C79.6552 114.44 79.5507 121.358 79.5507 123.486C79.5507 125.189 80.7132 127.212 83.8818 126.574C109.236 118.058 127.513 94.0055 127.513 65.5895C127.617 30.0408 98.9884 1.30612 63.8093 1.30612Z" fill="#24292F"/>
+</g>
+<defs>
+<clipPath id="clip0_0_48">
+<rect width="128" height="125.388" fill="white" transform="translate(0 1.30612)"/>
+</clipPath>
+</defs>
+</svg>

+ 1 - 0
public/provider-logos/gmail.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" fill="none" viewBox="0 0 192 192"><path fill="url(#a)" d="M146 44h38v110c0 6.627-5.373 12-12 12h-20a6 6 0 0 1-6-6z"/><path fill="#fc413d" d="M46 44H8v110c0 6.627 5.373 12 12 12h20a6 6 0 0 0 6-6z"/><path fill="url(#b)" d="M39.226 30.456c-8.033-6.752-20.018-5.714-26.77 2.319-6.752 8.032-5.714 20.017 2.319 26.77l76.078 63.949a8 8 0 0 0 10.295 0l76.078-63.95c8.032-6.752 9.07-18.737 2.318-26.77-6.752-8.032-18.737-9.07-26.769-2.318L96 78.18z"/><defs><linearGradient id="a" x1="165" x2="165" y1="44" y2="166" gradientUnits="userSpaceOnUse"><stop stop-color="#60d673"/><stop offset=".17" stop-color="#42c868"/><stop offset=".39" stop-color="#0ebc5f"/><stop offset=".62" stop-color="#00a9bb"/><stop offset=".86" stop-color="#3c90ff"/><stop offset="1" stop-color="#3186ff"/></linearGradient><linearGradient id="b" x1="8" x2="184" y1="46.13" y2="46.13" gradientUnits="userSpaceOnUse"><stop offset=".08" stop-color="#ff63a0"/><stop offset=".3" stop-color="#fc413d"/><stop offset=".5" stop-color="#fc413d"/><stop offset=".65" stop-color="#fc413d"/><stop offset=".72" stop-color="#fc5c30"/><stop offset=".86" stop-color="#feb10c"/><stop offset=".91" stop-color="#fec700"/><stop offset=".96" stop-color="#ffdb0f"/></linearGradient></defs></svg>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 89 - 0
public/provider-logos/googlecalendar.svg


+ 25 - 0
public/provider-logos/googledrive.svg

@@ -0,0 +1,25 @@
+<svg viewBox="0 0 800 741.3696" width="128" height="128" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+  <mask id="a" width="168" height="154" x="12" y="18" maskUnits="userSpaceOnUse">
+    <path fill="#fff" d="M63.09 37c14.626-25.333 51.193-25.334 65.819 0l45.033 78c14.626 25.334-3.657 57.001-32.91 57.001H50.967c-29.253 0-47.536-31.667-32.91-57.001Z"/>
+  </mask>
+  <g mask="url(#a)" transform="matrix(4.8140532,0,0,4.8140532,-62.146701,-86.652356)">
+    <path fill="url(#b)" d="M206.905 172.02h-91.888l-19.015-32.934 45.944-79.578Z"/>
+    <path fill="url(#c)" d="M-14.919 172.006 50.04 59.494v.002L31.032 92.422h38.02L115 172.004l-129.918.001Z"/>
+    <path fill="url(#d)" d="M96.007-20.085 141.954 59.5l-19.011 32.928H31.048Z"/>
+  </g>
+  <defs>
+    <linearGradient id="b" x1="193.6" x2="103.09" y1="165.6" y2="111.21" gradientUnits="userSpaceOnUse">
+      <stop offset=".09" stop-color="#ffe921"/>
+      <stop offset="1" stop-color="#fec700"/>
+    </linearGradient>
+    <linearGradient id="c" x1="114.4" x2="15.53" y1="181.61" y2="121.8" gradientUnits="userSpaceOnUse">
+      <stop offset=".15" stop-color="#a9a8ff"/>
+      <stop offset=".33" stop-color="#6d97ff"/>
+      <stop offset=".48" stop-color="#3186ff"/>
+    </linearGradient>
+    <linearGradient id="d" x1="128.88" x2="28.7" y1="37.88" y2="84.64" gradientUnits="userSpaceOnUse">
+      <stop offset=".55" stop-color="#0ebc5f"/>
+      <stop offset=".85" stop-color="#78c9ff"/>
+    </linearGradient>
+  </defs>
+</svg>

+ 5 - 0
public/provider-logos/linear.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 16 16" fill="#8299FF">
+  <path
+    d="M2.147 9.383c-.027-.114.109-.186.192-.103L6.72 13.66c.083.083.011.219-.103.192a6.015 6.015 0 0 1-4.47-4.47ZM2 7.627a.119.119 0 0 0 .035.091l6.247 6.247a.119.119 0 0 0 .091.035c.285-.018.564-.055.836-.111a.117.117 0 0 0 .057-.198L2.31 6.734a.117.117 0 0 0-.198.057 6.007 6.007 0 0 0-.11.836ZM2.505 5.565a.119.119 0 0 0 .025.132l7.773 7.773a.118.118 0 0 0 .132.025c.215-.096.422-.203.623-.322a.118.118 0 0 0 .022-.185L3.012 4.92a.118.118 0 0 0-.185.022c-.119.2-.226.408-.322.623ZM3.519 4.169a.118.118 0 0 1-.005-.163 6.006 6.006 0 1 1 8.48 8.48.118.118 0 0 1-.163-.005L3.52 4.169Z"
+  />
+</svg>

+ 11 - 0
public/provider-logos/notion.svg

@@ -0,0 +1,11 @@
+<svg width="128" height="128" viewBox="0 0 335 350" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_913_11)">
+<path d="M21.056 15.0565L214.701 0.796296C238.481 -1.24326 244.599 0.122938 259.545 10.9802L321.359 54.4257C331.558 61.8966 334.958 63.9307 334.958 72.0749V310.358C334.958 325.292 329.518 334.124 310.498 335.475L85.6192 349.054C71.3416 349.735 64.5464 347.701 57.0693 338.19L11.5487 279.13C3.3919 268.259 0 260.126 0 250.61V38.8077C0 26.5954 5.44153 16.4088 21.056 15.0565Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M214.701 0.796296L21.056 15.0565C5.44153 16.4088 0 26.5954 0 38.8077V250.61C0 260.126 3.3919 268.259 11.5487 279.13L57.0693 338.19C64.5464 347.701 71.3416 349.735 85.6192 349.054L310.498 335.475C329.518 334.124 334.958 325.292 334.958 310.358V72.0749C334.958 64.3554 331.904 62.1254 322.899 55.5518C322.404 55.1901 321.891 54.8153 321.359 54.4257L259.545 10.9802C244.599 0.122938 238.481 -1.24326 214.701 0.796296ZM90.718 68.1664C72.3581 69.4062 68.1862 69.6879 57.7599 61.2177L31.2457 40.1737C28.5415 37.4525 29.8978 34.0579 36.6871 33.3847L222.855 19.8061C238.478 18.4455 246.629 23.8852 252.747 28.635L284.678 51.7183C286.038 52.3944 289.429 56.4624 285.351 56.4624L93.0882 68.0071C92.2729 68.0614 91.4837 68.1147 90.719 68.1664L90.718 68.1664ZM69.3031 308.318V106.024C69.3031 97.2006 72.021 93.1245 80.1694 92.4398L300.977 79.5458C308.466 78.867 311.857 83.6223 311.857 92.4398V293.376C311.857 302.208 310.495 309.686 298.261 310.358L86.9643 322.584C74.7361 323.257 69.3031 319.19 69.3031 308.318ZM277.894 116.876C279.249 122.992 277.894 129.102 271.768 129.789L261.586 131.817V281.164C252.747 285.915 244.596 288.631 237.804 288.631C226.929 288.631 224.206 285.234 216.06 275.056L149.467 170.513V271.662L170.539 276.417C170.539 276.417 170.539 288.631 153.538 288.631L106.67 291.349C105.308 288.631 106.67 281.848 111.424 280.488L123.654 277.099V143.362L106.672 142.001C105.311 135.886 108.703 127.068 118.221 126.383L168.501 122.994L237.804 228.898V135.212L220.135 133.184C218.778 125.707 224.206 120.278 231.001 119.605L277.894 116.876Z" fill="black"/>
+</g>
+<defs>
+<clipPath id="clip0_913_11">
+<rect width="335" height="349.948" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 76 - 0
public/provider-logos/outlook.svg

@@ -0,0 +1,76 @@
+<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="60 90.4 570.02 539.67">
+<defs>
+<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="9.98908" y1="22.364901" x2="30.932199" y2="9.37495" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0" style="stop-color:rgb(12.54902%,65.490196%,98.039216%);stop-opacity:1;"/>
+<stop offset="0.4" style="stop-color:rgb(23.137255%,83.529412%,100%);stop-opacity:1;"/>
+<stop offset="1" style="stop-color:rgb(76.862745%,69.019608%,100%);stop-opacity:1;"/>
+</linearGradient>
+<linearGradient id="linear1" gradientUnits="userSpaceOnUse" x1="17.197201" y1="26.7945" x2="28.856199" y2="8.12575" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0" style="stop-color:rgb(8.627451%,35.294118%,85.098039%);stop-opacity:1;"/>
+<stop offset="0.5008" style="stop-color:rgb(9.411765%,50.196078%,89.803922%);stop-opacity:1;"/>
+<stop offset="1" style="stop-color:rgb(52.156863%,52.941176%,100%);stop-opacity:1;"/>
+</linearGradient>
+<linearGradient id="linear2" gradientUnits="userSpaceOnUse" x1="25.7005" y1="27.048401" x2="12.7563" y2="16.501301" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0.236946" style="stop-color:rgb(26.666667%,54.117647%,100%);stop-opacity:0;"/>
+<stop offset="0.792113" style="stop-color:rgb(0%,19.607843%,69.411765%);stop-opacity:0.2;"/>
+</linearGradient>
+<linearGradient id="linear3" gradientUnits="userSpaceOnUse" x1="24.0534" y1="31.1099" x2="44.509998" y2="18.0177" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0" style="stop-color:rgb(10.196078%,26.27451%,65.098039%);stop-opacity:1;"/>
+<stop offset="0.492267" style="stop-color:rgb(12.54902%,32.156863%,79.607843%);stop-opacity:1;"/>
+<stop offset="1" style="stop-color:rgb(37.254902%,12.54902%,79.607843%);stop-opacity:1;"/>
+</linearGradient>
+<linearGradient id="linear4" gradientUnits="userSpaceOnUse" x1="29.8281" y1="30.327299" x2="17.397499" y2="19.570801" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0" style="stop-color:rgb(0%,27.058824%,72.54902%);stop-opacity:0;"/>
+<stop offset="0.669859" style="stop-color:rgb(5.098039%,12.156863%,41.176471%);stop-opacity:0.2;"/>
+</linearGradient>
+<radialGradient id="radial0" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(0.000000000000024802,-405.040512,438.393002,0.000000000000026844,360.027008,102.268202)">
+<stop offset="0.568182" style="stop-color:rgb(15.294118%,37.254902%,94.117647%);stop-opacity:0;"/>
+<stop offset="0.992424" style="stop-color:rgb(0%,12.941176%,46.666667%);stop-opacity:1;"/>
+</radialGradient>
+<linearGradient id="linear5" gradientUnits="userSpaceOnUse" x1="41.998001" y1="29.9431" x2="23.8517" y2="29.9431" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0" style="stop-color:rgb(30.196078%,76.862745%,100%);stop-opacity:1;"/>
+<stop offset="0.196145" style="stop-color:rgb(5.882353%,68.627451%,100%);stop-opacity:1;"/>
+</linearGradient>
+<radialGradient id="radial1" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(122.73959,-122.73959,122.73959,122.73959,421.392002,568.675518)">
+<stop offset="0.259477" style="stop-color:rgb(0%,37.647059%,81.960784%);stop-opacity:0.4;"/>
+<stop offset="0.908166" style="stop-color:rgb(1.176471%,51.372549%,94.509804%);stop-opacity:0;"/>
+</radialGradient>
+<radialGradient id="radial2" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(357.407022,-468.445926,423.594568,323.187085,159.471002,697.080002)">
+<stop offset="0.732317" style="stop-color:rgb(95.686275%,65.490196%,96.862745%);stop-opacity:0;"/>
+<stop offset="1" style="stop-color:rgb(95.686275%,65.490196%,96.862745%);stop-opacity:0.501961;"/>
+</radialGradient>
+<radialGradient id="radial3" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(-170.860868,259.725406,-674.018133,-443.404152,278.562012,412.978506)">
+<stop offset="0" style="stop-color:rgb(28.627451%,87.058824%,100%);stop-opacity:1;"/>
+<stop offset="0.724349" style="stop-color:rgb(16.078431%,76.470588%,100%);stop-opacity:1;"/>
+</radialGradient>
+<linearGradient id="linear6" gradientUnits="userSpaceOnUse" x1="3.45756" y1="37.872299" x2="20.9291" y2="37.859699" gradientTransform="matrix(15,0,0,15,0,0)">
+<stop offset="0.205882" style="stop-color:rgb(42.352941%,87.843137%,100%);stop-opacity:1;"/>
+<stop offset="0.535" style="stop-color:rgb(31.372549%,83.529412%,100%);stop-opacity:0;"/>
+</linearGradient>
+<radialGradient id="radial4" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(215.76719,230.769125,-230.769125,215.76719,59.143649,354.231005)">
+<stop offset="0.038877" style="stop-color:rgb(0%,56.862745%,100%);stop-opacity:1;"/>
+<stop offset="0.919119" style="stop-color:rgb(9.411765%,23.921569%,67.843137%);stop-opacity:1;"/>
+</radialGradient>
+<radialGradient id="radial5" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(0.000000000000010287,167.999997,-193.782005,0.000000000000011866,180,491.158504)">
+<stop offset="0.557796" style="stop-color:rgb(5.882353%,64.705882%,96.862745%);stop-opacity:0;"/>
+<stop offset="1" style="stop-color:rgb(45.490196%,77.647059%,100%);stop-opacity:0.501961;"/>
+</radialGradient>
+</defs>
+<g id="surface1">
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 463.984375 140.144531 L 119.636719 358.414062 L 90.023438 311.695312 L 90.023438 271.4375 C 90.023438 256.78125 97.445312 243.121094 109.742188 235.144531 L 309.910156 105.257812 C 340.40625 85.46875 379.6875 85.464844 410.1875 105.25 Z M 463.984375 140.144531 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear1);" d="M 407.101562 103.339844 C 408.136719 103.953125 409.164062 104.59375 410.183594 105.253906 L 566.398438 206.585938 L 179.0625 452.105469 L 119.625 358.335938 L 403.894531 177.800781 C 430.820312 160.699219 432 122.230469 407.101562 103.339844 Z M 407.101562 103.339844 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear2);" d="M 407.101562 103.339844 C 408.136719 103.953125 409.164062 104.59375 410.183594 105.253906 L 566.398438 206.585938 L 179.0625 452.105469 L 119.625 358.335938 L 403.894531 177.800781 C 430.820312 160.699219 432 122.230469 407.101562 103.339844 Z M 407.101562 103.339844 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear3);" d="M 333.601562 498.988281 L 179.066406 452.109375 L 507.628906 243.835938 C 535.300781 226.296875 535.230469 185.898438 507.496094 168.457031 L 506.015625 167.527344 L 510.277344 170.175781 L 610.273438 235.042969 C 622.574219 243.019531 629.996094 256.683594 629.996094 271.34375 L 629.996094 310.304688 Z M 333.601562 498.988281 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear4);" d="M 333.601562 498.988281 L 179.066406 452.109375 L 507.628906 243.835938 C 535.300781 226.296875 535.230469 185.898438 507.496094 168.457031 L 506.015625 167.527344 L 510.277344 170.175781 L 610.273438 235.042969 C 622.574219 243.019531 629.996094 256.683594 629.996094 271.34375 L 629.996094 310.304688 Z M 333.601562 498.988281 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial0);" d="M 410.1875 105.25 C 379.6875 85.464844 340.40625 85.46875 309.90625 105.257812 L 109.742188 235.144531 C 97.445312 243.121094 90.023438 256.78125 90.023438 271.4375 L 90.023438 273.40625 C 90.507812 288.121094 98.25 301.679688 110.757812 309.566406 L 359.644531 466.476562 L 609.160156 309.804688 C 622.121094 301.667969 629.984375 287.441406 629.984375 272.140625 L 629.984375 310.308594 L 629.992188 271.34375 C 629.992188 256.683594 622.566406 243.023438 610.269531 235.042969 Z M 410.1875 105.25 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear5);" d="M 315.769531 630.050781 L 536.21875 630.050781 C 587.996094 630.050781 629.96875 588.078125 629.96875 536.300781 L 629.96875 272.140625 C 629.96875 287.441406 622.105469 301.667969 609.148438 309.804688 L 281.242188 515.695312 C 263.554688 526.804688 252.820312 546.222656 252.820312 567.109375 C 252.824219 601.871094 281.003906 630.050781 315.769531 630.050781 Z M 315.769531 630.050781 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial1);" d="M 315.769531 630.050781 L 536.21875 630.050781 C 587.996094 630.050781 629.96875 588.078125 629.96875 536.300781 L 629.96875 272.140625 C 629.96875 287.441406 622.105469 301.667969 609.148438 309.804688 L 281.242188 515.695312 C 263.554688 526.804688 252.820312 546.222656 252.820312 567.109375 C 252.824219 601.871094 281.003906 630.050781 315.769531 630.050781 Z M 315.769531 630.050781 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial2);" d="M 315.769531 630.050781 L 536.21875 630.050781 C 587.996094 630.050781 629.96875 588.078125 629.96875 536.300781 L 629.96875 272.140625 C 629.96875 287.441406 622.105469 301.667969 609.148438 309.804688 L 281.242188 515.695312 C 263.554688 526.804688 252.820312 546.222656 252.820312 567.109375 C 252.824219 601.871094 281.003906 630.050781 315.769531 630.050781 Z M 315.769531 630.050781 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial3);" d="M 405.402344 630.035156 L 183.738281 630.035156 C 131.960938 630.035156 89.988281 588.0625 89.988281 536.285156 L 89.988281 271.945312 C 89.988281 287.21875 97.824219 301.421875 110.742188 309.566406 L 438.324219 516.085938 C 456.257812 527.390625 467.132812 547.113281 467.132812 568.3125 C 467.128906 602.402344 439.492188 630.035156 405.402344 630.035156 Z M 405.402344 630.035156 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear6);" d="M 405.402344 630.035156 L 183.738281 630.035156 C 131.960938 630.035156 89.988281 588.0625 89.988281 536.285156 L 89.988281 271.945312 C 89.988281 287.21875 97.824219 301.421875 110.742188 309.566406 L 438.324219 516.085938 C 456.257812 527.390625 467.132812 547.113281 467.132812 568.3125 C 467.128906 602.402344 439.492188 630.035156 405.402344 630.035156 Z M 405.402344 630.035156 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial4);" d="M 108.75 345 L 251.25 345 C 278.175781 345 300 366.824219 300 393.75 L 300 536.25 C 300 563.175781 278.175781 585 251.25 585 L 108.75 585 C 81.824219 585 60 563.175781 60 536.25 L 60 393.75 C 60 366.824219 81.824219 345 108.75 345 Z M 108.75 345 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial5);" d="M 108.75 345 L 251.25 345 C 278.175781 345 300 366.824219 300 393.75 L 300 536.25 C 300 563.175781 278.175781 585 251.25 585 L 108.75 585 C 81.824219 585 60 563.175781 60 536.25 L 60 393.75 C 60 366.824219 81.824219 345 108.75 345 Z M 108.75 345 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 179.386719 534 C 159.539062 534 143.25 527.789062 130.511719 515.375 C 117.773438 502.960938 111.402344 486.757812 111.402344 466.769531 C 111.402344 445.660156 117.867188 428.589844 130.796875 415.550781 C 143.730469 402.515625 160.660156 396 181.59375 396 C 201.375 396 217.472656 402.238281 229.890625 414.714844 C 242.375 427.191406 248.617188 443.644531 248.617188 464.066406 C 248.617188 485.050781 242.148438 501.964844 229.21875 514.816406 C 216.351562 527.605469 199.742188 534 179.386719 534 Z M 179.960938 507.648438 C 190.777344 507.648438 199.484375 503.953125 206.078125 496.566406 C 212.671875 489.179688 215.96875 478.902344 215.96875 465.742188 C 215.96875 452.023438 212.765625 441.347656 206.367188 433.710938 C 199.964844 426.074219 191.417969 422.257812 180.730469 422.257812 C 169.71875 422.257812 160.851562 426.199219 154.132812 434.082031 C 147.410156 441.90625 144.050781 452.273438 144.050781 465.183594 C 144.050781 478.285156 147.410156 488.652344 154.132812 496.285156 C 160.851562 503.859375 169.460938 507.648438 179.960938 507.648438 Z M 179.960938 507.648438 "/>
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 179.332031 535.847656 C 159.5625 535.847656 143.332031 529.472656 130.640625 516.71875 C 117.953125 503.964844 111.605469 487.320312 111.605469 466.789062 C 111.605469 445.105469 118.046875 427.570312 130.929688 414.179688 C 143.8125 400.785156 160.679688 394.089844 181.53125 394.089844 C 201.234375 394.089844 217.273438 400.5 229.644531 413.316406 C 242.082031 426.136719 248.296875 443.035156 248.296875 464.015625 C 248.296875 485.566406 241.855469 502.945312 228.976562 516.144531 C 216.15625 529.28125 199.609375 535.847656 179.332031 535.847656 Z M 179.902344 508.78125 C 190.679688 508.78125 199.355469 504.984375 205.921875 497.398438 C 212.492188 489.808594 215.773438 479.253906 215.773438 465.734375 C 215.773438 451.640625 212.585938 440.675781 206.210938 432.832031 C 199.832031 424.988281 191.320312 421.066406 180.671875 421.066406 C 169.699219 421.066406 160.867188 425.113281 154.171875 433.214844 C 147.476562 441.246094 144.128906 451.898438 144.128906 465.160156 C 144.128906 478.617188 147.476562 489.265625 154.171875 497.109375 C 160.867188 504.890625 169.445312 508.78125 179.902344 508.78125 Z M 179.902344 508.78125 "/>
+</g>
+</svg>

+ 22 - 0
public/provider-logos/slack.svg

@@ -0,0 +1,22 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_0_113)">
+<g clip-path="url(#clip1_0_113)">
+<path d="M26.8925 80.886C26.8925 88.2866 20.8469 94.3323 13.4463 94.3323C6.04561 94.3323 0 88.2866 0 80.886C0 73.4853 6.04561 67.4397 13.4463 67.4397H26.8925V80.886Z" fill="#E01E5A"/>
+<path d="M33.6675 80.886C33.6675 73.4853 39.7131 67.4397 47.1137 67.4397C54.5144 67.4397 60.56 73.4853 60.56 80.886V114.554C60.56 121.954 54.5144 128 47.1137 128C39.7131 128 33.6675 121.954 33.6675 114.554V80.886Z" fill="#E01E5A"/>
+<path d="M47.1137 26.8925C39.7131 26.8925 33.6675 20.8469 33.6675 13.4463C33.6675 6.0456 39.7131 -3.8147e-06 47.1137 -3.8147e-06C54.5144 -3.8147e-06 60.56 6.0456 60.56 13.4463V26.8925H47.1137Z" fill="#36C5F0"/>
+<path d="M47.114 33.6678C54.5147 33.6678 60.5603 39.7134 60.5603 47.114C60.5603 54.5147 54.5147 60.5603 47.114 60.5603H13.4463C6.0456 60.5603 0 54.5147 0 47.114C0 39.7134 6.0456 33.6678 13.4463 33.6678H47.114Z" fill="#36C5F0"/>
+<path d="M101.107 47.114C101.107 39.7134 107.153 33.6678 114.554 33.6678C121.954 33.6678 128 39.7134 128 47.114C128 54.5147 121.954 60.5603 114.554 60.5603H101.107V47.114Z" fill="#2EB67D"/>
+<path d="M94.3322 47.114C94.3322 54.5147 88.2866 60.5603 80.8859 60.5603C73.4853 60.5603 67.4397 54.5147 67.4397 47.114V13.4463C67.4397 6.0456 73.4853 -3.8147e-06 80.8859 -3.8147e-06C88.2866 -3.8147e-06 94.3322 6.0456 94.3322 13.4463V47.114Z" fill="#2EB67D"/>
+<path d="M80.8859 101.107C88.2866 101.107 94.3322 107.153 94.3322 114.554C94.3322 121.954 88.2866 128 80.8859 128C73.4853 128 67.4397 121.954 67.4397 114.554V101.107H80.8859Z" fill="#ECB22E"/>
+<path d="M80.8859 94.3323C73.4853 94.3323 67.4397 88.2866 67.4397 80.886C67.4397 73.4853 73.4853 67.4397 80.8859 67.4397H114.554C121.954 67.4397 128 73.4853 128 80.886C128 88.2866 121.954 94.3323 114.554 94.3323H80.8859Z" fill="#ECB22E"/>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_0_113">
+<rect width="128" height="128" fill="white"/>
+</clipPath>
+<clipPath id="clip1_0_113">
+<rect width="128" height="128" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 19 - 0
public/provider-logos/trello.svg

@@ -0,0 +1,19 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_240_7025)">
+<g clip-path="url(#clip1_240_7025)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M112.867 0H15.133C6.72578 0 0 6.72578 0 15.133V112.657C0 121.064 6.72578 127.79 15.133 127.79H112.657C121.064 127.79 127.79 121.064 127.79 112.657V15.3432C128 6.72578 121.274 0 112.867 0ZM55.0673 92.2693C55.0673 95.0016 52.7553 97.3136 50.023 97.3136H28.7947C26.0624 97.3136 23.7504 95.0016 23.7504 92.2693V28.7947C23.7504 26.0624 26.0624 23.7504 28.7947 23.7504H50.2332C52.9655 23.7504 55.2775 26.0624 55.2775 28.7947V92.2693H55.0673ZM104.46 63.0542C104.46 65.7865 102.358 68.0985 99.4154 68.3087C99.4154 68.3087 99.4154 68.3087 99.2053 68.3087H77.977C75.2447 68.3087 72.9327 65.9967 72.9327 63.2644V28.7947C72.9327 26.0624 75.2447 23.7504 77.977 23.7504H99.4154C102.148 23.7504 104.46 26.0624 104.46 28.7947V63.0542Z" fill="url(#paint0_linear_240_7025)"/>
+</g>
+</g>
+<defs>
+<linearGradient id="paint0_linear_240_7025" x1="64.042" y1="128.084" x2="64.042" y2="0" gradientUnits="userSpaceOnUse">
+<stop stop-color="#0052CC"/>
+<stop offset="1" stop-color="#2684FF"/>
+</linearGradient>
+<clipPath id="clip0_240_7025">
+<rect width="128" height="128" fill="white"/>
+</clipPath>
+<clipPath id="clip1_240_7025">
+<rect width="128" height="128" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 128 - 0
public/provider-logos/twitter.svg

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 1024.00 1024.00" width="128" height="128">
+<g stroke-width="2.00" fill="none" stroke-linecap="butt">
+<path stroke="#808080" vector-effect="non-scaling-stroke" d="
+  M 826.22 795.00
+  L 579.51 468.80
+  A 0.48 0.48 0.0 0 1 579.53 468.19
+  L 789.06 228.72
+  A 0.33 0.33 0.0 0 0 788.81 228.17
+  L 694.40 228.17
+  A 2.46 2.44 20.6 0 0 692.56 229.00
+  L 535.08 408.98
+  A 0.54 0.54 0.0 0 1 534.24 408.95
+  L 398.04 228.87
+  A 1.80 1.79 71.3 0 0 396.61 228.16
+  L 199.90 228.16
+  A 0.50 0.50 0.0 0 0 199.50 228.96
+  L 435.78 537.86
+  A 1.00 1.00 0.0 0 1 435.74 539.13
+  L 211.82 795.03
+  A 0.30 0.29 20.3 0 0 212.05 795.52
+  L 306.94 795.52
+  A 1.61 1.60 -69.1 0 0 308.14 794.98
+  L 480.70 597.76
+  A 0.59 0.59 0.0 0 1 481.62 597.79
+  L 632.49 795.03
+  A 1.27 1.27 0.0 0 0 633.49 795.52
+  L 825.96 795.52
+  A 0.33 0.32 -18.7 0 0 826.22 795.00"
+/>
+<path stroke="#808080" vector-effect="non-scaling-stroke" d="
+  M 712.09 737.88
+  A 0.33 0.33 0.0 0 0 712.35 737.35
+  L 368.64 282.92
+  A 0.33 0.33 0.0 0 0 368.38 282.79
+  L 312.01 282.79
+  A 0.33 0.33 0.0 0 0 311.75 283.32
+  L 659.34 737.75
+  A 0.33 0.33 0.0 0 0 659.60 737.88
+  L 712.09 737.88"
+/>
+</g>
+<path fill="#000000" d="
+  M 1024.00 0.00
+  L 1024.00 1024.00
+  L 0.00 1024.00
+  L 0.00 0.00
+  L 1024.00 0.00
+  Z
+  M 826.22 795.00
+  L 579.51 468.80
+  A 0.48 0.48 0.0 0 1 579.53 468.19
+  L 789.06 228.72
+  A 0.33 0.33 0.0 0 0 788.81 228.17
+  L 694.40 228.17
+  A 2.46 2.44 20.6 0 0 692.56 229.00
+  L 535.08 408.98
+  A 0.54 0.54 0.0 0 1 534.24 408.95
+  L 398.04 228.87
+  A 1.80 1.79 71.3 0 0 396.61 228.16
+  L 199.90 228.16
+  A 0.50 0.50 0.0 0 0 199.50 228.96
+  L 435.78 537.86
+  A 1.00 1.00 0.0 0 1 435.74 539.13
+  L 211.82 795.03
+  A 0.30 0.29 20.3 0 0 212.05 795.52
+  L 306.94 795.52
+  A 1.61 1.60 -69.1 0 0 308.14 794.98
+  L 480.70 597.76
+  A 0.59 0.59 0.0 0 1 481.62 597.79
+  L 632.49 795.03
+  A 1.27 1.27 0.0 0 0 633.49 795.52
+  L 825.96 795.52
+  A 0.33 0.32 -18.7 0 0 826.22 795.00
+  Z"
+/>
+<path fill="#ffffff" d="
+  M 826.22 795.00
+  A 0.33 0.32 -18.7 0 1 825.96 795.52
+  L 633.49 795.52
+  A 1.27 1.27 0.0 0 1 632.49 795.03
+  L 481.62 597.79
+  A 0.59 0.59 0.0 0 0 480.70 597.76
+  L 308.14 794.98
+  A 1.61 1.60 -69.1 0 1 306.94 795.52
+  L 212.05 795.52
+  A 0.30 0.29 20.3 0 1 211.82 795.03
+  L 435.74 539.13
+  A 1.00 1.00 0.0 0 0 435.78 537.86
+  L 199.50 228.96
+  A 0.50 0.50 0.0 0 1 199.90 228.16
+  L 396.61 228.16
+  A 1.80 1.79 71.3 0 1 398.04 228.87
+  L 534.24 408.95
+  A 0.54 0.54 0.0 0 0 535.08 408.98
+  L 692.56 229.00
+  A 2.46 2.44 20.6 0 1 694.40 228.17
+  L 788.81 228.17
+  A 0.33 0.33 0.0 0 1 789.06 228.72
+  L 579.53 468.19
+  A 0.48 0.48 0.0 0 0 579.51 468.80
+  L 826.22 795.00
+  Z
+  M 712.09 737.88
+  A 0.33 0.33 0.0 0 0 712.35 737.35
+  L 368.64 282.92
+  A 0.33 0.33 0.0 0 0 368.38 282.79
+  L 312.01 282.79
+  A 0.33 0.33 0.0 0 0 311.75 283.32
+  L 659.34 737.75
+  A 0.33 0.33 0.0 0 0 659.60 737.88
+  L 712.09 737.88
+  Z"
+/>
+<path fill="#000000" d="
+  M 712.09 737.88
+  L 659.60 737.88
+  A 0.33 0.33 0.0 0 1 659.34 737.75
+  L 311.75 283.32
+  A 0.33 0.33 0.0 0 1 312.01 282.79
+  L 368.38 282.79
+  A 0.33 0.33 0.0 0 1 368.64 282.92
+  L 712.35 737.35
+  A 0.33 0.33 0.0 0 1 712.09 737.88
+  Z"
+/>
+</svg>

+ 27 - 0
src/client/components/common/ProviderLogo.vue

@@ -0,0 +1,27 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue'
+import { safeProviderLogo } from '@shared/botProviderLogos'
+
+const props = defineProps<{ name: string; logo?: string; bundledLogo?: string }>()
+const failed = ref<string[]>([])
+const source = computed(() => [props.bundledLogo, props.logo].map(safeProviderLogo).find((url): url is string => !!url && !failed.value.includes(url)))
+const initial = computed(() => Array.from(props.name.trim())[0]?.toLocaleUpperCase() || '?')
+watch(() => [props.logo, props.bundledLogo], () => { failed.value = [] })
+function onError(event: Event) {
+  const url = (event.target as HTMLImageElement).getAttribute('src')
+  if (url && !failed.value.includes(url)) failed.value.push(url)
+}
+</script>
+
+<template>
+  <span class="provider-logo" :class="{ 'provider-logo--fallback': !source }" aria-hidden="true">
+    <img v-if="source" :key="source" :src="source" alt="" width="28" height="28" loading="lazy" decoding="async" referrerpolicy="no-referrer" :draggable="false" @error="onError" />
+    <span v-else>{{ initial }}</span>
+  </span>
+</template>
+
+<style scoped>
+.provider-logo{display:grid;place-items:center;box-sizing:border-box;width:40px;height:40px;flex:0 0 40px;overflow:hidden;border:1px solid var(--line);border-radius:10px;background:#fff}
+.provider-logo img{display:block;width:28px;height:28px;object-fit:contain}
+.provider-logo--fallback{background:var(--surface-soft);color:var(--text-primary);font-size:17px;font-weight:650}
+</style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 2
src/client/components/workspace/BotPluginsPanel.vue


+ 3 - 2
src/server/botPlugins/connectedApps.ts

@@ -4,6 +4,7 @@ import type { WorkspaceStore } from '../workspaceStore.js'
 import type { WorkspaceNodes } from '../workspaceGateway.js'
 import type { WorkspaceNodes } from '../workspaceGateway.js'
 import { HttpError } from '../errors.js'
 import { HttpError } from '../errors.js'
 import type { BotAppCard, BotAppConnection, BotPluginSettings } from '../../shared/workspacePlugins.js'
 import type { BotAppCard, BotAppConnection, BotPluginSettings } from '../../shared/workspacePlugins.js'
+import { bundledProviderLogo, safeProviderLogo } from '../../shared/botProviderLogos.js'
 
 
 const API = 'https://backend.composio.dev/api/v3.1'
 const API = 'https://backend.composio.dev/api/v3.1'
 const CATALOG_API = 'https://backend.composio.dev/api/v3'
 const CATALOG_API = 'https://backend.composio.dev/api/v3'
@@ -16,7 +17,7 @@ export const CURATED_APPS: BotAppCard[] = [
   ['github', 'GitHub', '代码仓库与议题'], ['notion', 'Notion', '文档与知识库'], ['slack', 'Slack', '团队沟通'],
   ['github', 'GitHub', '代码仓库与议题'], ['notion', 'Notion', '文档与知识库'], ['slack', 'Slack', '团队沟通'],
   ['linear', 'Linear', '项目与任务'], ['discord', 'Discord', '社区消息'], ['outlook', 'Outlook', '邮件与日历'],
   ['linear', 'Linear', '项目与任务'], ['discord', 'Discord', '社区消息'], ['outlook', 'Outlook', '邮件与日历'],
   ['trello', 'Trello', '看板与任务'], ['airtable', 'Airtable', '表格与业务数据'], ['twitter', 'X', '社交内容'],
   ['trello', 'Trello', '看板与任务'], ['airtable', 'Airtable', '表格与业务数据'], ['twitter', 'X', '社交内容'],
-].map(([slug, name, description]) => ({ slug: slug!, name: name!, description: description! }))
+].map(([slug, name, description]) => ({ slug: slug!, name: name!, description: description!, logo: bundledProviderLogo(slug!) }))
 
 
 function trustedUrl(value: string): string {
 function trustedUrl(value: string): string {
   let url: URL
   let url: URL
@@ -81,7 +82,7 @@ export class ConnectedApps {
     if (value?.key) try {
     if (value?.key) try {
       const items = await this.pages(value.key, '/toolkits', { limit: '500', sort_by: 'usage' }, undefined, CATALOG_API)
       const items = await this.pages(value.key, '/toolkits', { limit: '500', sort_by: 'usage' }, undefined, CATALOG_API)
       this.current(owner, value.revision)
       this.current(owner, value.revision)
-      const cards = items.filter(item => appSlug.safeParse(item.slug).success).map(item => ({ slug: item.slug, name: String(item.name ?? item.slug).slice(0, 100), description: String(item.meta?.description ?? item.description ?? '').slice(0, 180) }))
+      const cards = items.filter(item => appSlug.safeParse(item.slug).success).map(item => ({ slug: item.slug, name: String(item.name ?? item.slug).slice(0, 100), description: String(item.meta?.description ?? item.description ?? '').slice(0, 180), logo: safeProviderLogo(item.meta?.logo ?? item.logo) ?? bundledProviderLogo(item.slug) }))
       if (cards.length) { this.catalogCache.set(owner, { at: Date.now(), cards }); return { cards, source: 'api' } }
       if (cards.length) { this.catalogCache.set(owner, { at: Date.now(), cards }); return { cards, source: 'api' } }
     } catch { /* Catalog fallback never claims that account inventory is empty. */ }
     } catch { /* Catalog fallback never claims that account inventory is empty. */ }
     return { cards: CURATED_APPS, source: 'curated' }
     return { cards: CURATED_APPS, source: 'curated' }

+ 2 - 1
src/server/security.ts

@@ -4,6 +4,7 @@ import type Koa from 'koa'
 import { parse, serialize } from 'cookie'
 import { parse, serialize } from 'cookie'
 import type { ServerConfig } from './config.js'
 import type { ServerConfig } from './config.js'
 import { isLoopbackHost, isPrivateHost } from './config.js'
 import { isLoopbackHost, isPrivateHost } from './config.js'
+import { BOT_LOGO_ORIGINS } from '../shared/botProviderLogos.js'
 
 
 export const CSRF_COOKIE = 'hermes_yaoyao_csrf'
 export const CSRF_COOKIE = 'hermes_yaoyao_csrf'
 
 
@@ -183,7 +184,7 @@ export function applySecurityHeaders(ctx: Koa.Context, tls: boolean, allowedHost
     "object-src 'none'",
     "object-src 'none'",
     "script-src 'self'",
     "script-src 'self'",
     "style-src 'self' 'unsafe-inline'",
     "style-src 'self' 'unsafe-inline'",
-    "img-src 'self' data: blob:",
+    `img-src 'self' data: blob: ${BOT_LOGO_ORIGINS.join(' ')}`,
     "media-src 'self' blob:",
     "media-src 'self' blob:",
     `connect-src 'self' ${socketOrigin} ${proxySocketOrigins.join(' ')}`.trim(),
     `connect-src 'self' ${socketOrigin} ${proxySocketOrigins.join(' ')}`.trim(),
   ].join('; '))
   ].join('; '))

+ 57 - 0
src/shared/botProviderLogos.ts

@@ -0,0 +1,57 @@
+import type { BotMcpPlugin } from './workspacePlugins.js'
+
+/** Bundled logos keep the built-in directory usable without a third-party request. */
+const providers: Record<string, { hosts: string[] }> = {
+  gmail: { hosts: ['gmail.com'] },
+  googledrive: { hosts: ['drive.google.com'] },
+  googlecalendar: { hosts: ['calendar.google.com'] },
+  github: { hosts: ['github.com', 'githubcopilot.com'] },
+  notion: { hosts: ['notion.so', 'notion.com'] },
+  slack: { hosts: ['slack.com'] },
+  linear: { hosts: ['linear.app'] },
+  discord: { hosts: ['discord.com'] },
+  outlook: { hosts: ['outlook.com', 'outlook.office.com'] },
+  trello: { hosts: ['trello.com'] },
+  airtable: { hosts: ['airtable.com'] },
+  twitter: { hosts: ['twitter.com', 'x.com'] },
+}
+const aliases: Record<string, string> = { gdrive: 'googledrive', x: 'twitter', microsoftoutlook: 'outlook' }
+
+export const BOT_LOGO_ORIGINS = ['https://logos.composio.dev', 'https://assets.composio.dev'] as const
+
+export function bundledProviderLogo(slug: string): string | undefined {
+  const key = slug.toLowerCase().replace(/[\s_-]/g, '')
+  const provider = aliases[key] ?? key
+  return Object.hasOwn(providers, provider) ? `/provider-logos/${provider}.svg` : undefined
+}
+
+/** Match the image CSP; never forward credentials or arbitrary image origins. */
+export function safeProviderLogo(value: unknown): string | undefined {
+  if (typeof value !== 'string' || value.length > 2048) return undefined
+  if (Object.keys(providers).some(slug => value === bundledProviderLogo(slug))) return value
+  try {
+    const url = new URL(value)
+    if (url.username || url.password || !BOT_LOGO_ORIGINS.some(origin => url.origin === origin)) return undefined
+    return url.href
+  } catch { return undefined }
+}
+
+export function mcpProviderLogo(plugin: BotMcpPlugin): string | undefined {
+  if (plugin.transport === 'http' && plugin.url) {
+    try {
+      const host = new URL(plugin.url).hostname.toLowerCase()
+      const provider = Object.entries(providers).find(([, { hosts }]) => hosts.some(domain => host === domain || host.endsWith(`.${domain}`)))
+      if (provider) return bundledProviderLogo(provider[0])
+    } catch { /* Older invalid definitions still get a readable placeholder. */ }
+  }
+  if (plugin.transport === 'stdio') {
+    for (const arg of [plugin.command ?? '', ...(plugin.args ?? [])]) {
+      const packageName = arg.toLowerCase().replace(/^--package=/, '').replace(/@[^/@]+$/, '')
+      if (packageName === '@notionhq/notion-mcp-server') return bundledProviderLogo('notion')
+      const slug = /^@modelcontextprotocol\/server-([a-z-]+)$/.exec(packageName)?.[1]
+      const logo = slug && bundledProviderLogo(slug)
+      if (logo) return logo
+    }
+  }
+  return bundledProviderLogo(plugin.name.replace(/\s*(?:mcp(?:\s*(?:server|服务))?|server|服务)$/i, '').trim())
+}

+ 1 - 1
src/shared/workspacePlugins.ts

@@ -13,7 +13,7 @@ export interface BotMcpPlugin {
   testedAt?: number
   testedAt?: number
   toolCount: number
   toolCount: number
 }
 }
-export interface BotAppCard { slug: string; name: string; description: string }
+export interface BotAppCard { slug: string; name: string; description: string; logo?: string }
 export interface BotAppAccount { id: string; alias?: string; status: string }
 export interface BotAppAccount { id: string; alias?: string; status: string }
 export interface BotAppConnection { slug: string; accounts: BotAppAccount[]; agentIds: string[]; revision: number }
 export interface BotAppConnection { slug: string; accounts: BotAppAccount[]; agentIds: string[]; revision: number }
 export interface BotPluginSettings { configured: boolean; revision: number }
 export interface BotPluginSettings { configured: boolean; revision: number }

+ 20 - 0
tests/client/botPluginsPanel.test.ts

@@ -36,6 +36,26 @@ it('retains connected accounts on failed refresh instead of presenting them as d
   expect(button(wrapper, '添加账号').exists()).toBe(true)
   expect(button(wrapper, '添加账号').exists()).toBe(true)
   wrapper.unmount()
   wrapper.unmount()
 })
 })
+it('shows the same supplier logo in the directory and connected list', async () => {
+  const wrapper = render(); await flushPromises()
+  expect(wrapper.get('.app-card .provider-logo img').attributes('src')).toBe('/provider-logos/gmail.svg')
+  await button(wrapper, '已连接 1').trigger('click')
+  expect(wrapper.get('.app-card .provider-logo img').attributes('src')).toBe('/provider-logos/gmail.svg')
+  wrapper.unmount()
+})
+it('shows a recognized MCP supplier logo and a placeholder for unknown services', async () => {
+  const request = vi.mocked(apiRequest).getMockImplementation()!
+  vi.mocked(apiRequest).mockImplementation(async (path, options) => path.endsWith('/mcp')
+    ? { plugins: [{ ...plugin, name: 'GitHub MCP', url: 'https://api.githubcopilot.com/mcp' }, { ...plugin, id: 'unknown' }], canManage } as never
+    : request(path, options))
+  const wrapper = render(); await flushPromises()
+  await button(wrapper, 'MCP 服务').trigger('click')
+  const logos = wrapper.findAll('.mcp-card .provider-logo')
+  expect(logos[0].get('img').attributes('src')).toBe('/provider-logos/github.svg')
+  expect(logos[1].find('img').exists()).toBe(false)
+  expect(logos[1].text()).toBe('现')
+  wrapper.unmount()
+})
 it('keeps secret placeholders and invalid form input without submitting a broken configuration', async () => {
 it('keeps secret placeholders and invalid form input without submitting a broken configuration', async () => {
   const wrapper = render(); await flushPromises()
   const wrapper = render(); await flushPromises()
   await button(wrapper, 'MCP 服务').trigger('click')
   await button(wrapper, 'MCP 服务').trigger('click')

+ 37 - 0
tests/client/providerLogo.test.ts

@@ -0,0 +1,37 @@
+import { mount } from '@vue/test-utils'
+import { expect, it } from 'vitest'
+import ProviderLogo from '@/components/common/ProviderLogo.vue'
+import { mcpProviderLogo } from '@shared/botProviderLogos'
+import type { BotMcpPlugin } from '@shared/workspacePlugins'
+
+it('uses bundled logos offline and falls back through the catalog logo to an initial', async () => {
+  const remote = 'https://logos.composio.dev/api/github'
+  const wrapper = mount(ProviderLogo, { props: { name: 'GitHub', logo: remote, bundledLogo: '/provider-logos/github.svg' } })
+  expect(wrapper.get('img').attributes('src')).toBe('/provider-logos/github.svg')
+  await wrapper.get('img').trigger('error')
+  expect(wrapper.get('img').attributes('src')).toBe(remote)
+  expect(wrapper.get('img').attributes('referrerpolicy')).toBe('no-referrer')
+  await wrapper.get('img').trigger('error')
+  expect(wrapper.find('img').exists()).toBe(false)
+  expect(wrapper.text()).toBe('G')
+  await wrapper.setProps({ logo: 'https://logos.composio.dev/api/github?version=2' })
+  expect(wrapper.get('img').attributes('src')).toBe('/provider-logos/github.svg')
+  wrapper.unmount()
+})
+
+it.each(['javascript:alert(1)', 'https://unknown.example/logo.svg', 'https://user:secret@logos.composio.dev/api/github', '//logos.composio.dev/api/github'])('keeps a readable placeholder for an unsupported logo: %s', logo => {
+  const wrapper = mount(ProviderLogo, { props: { name: '供应商', logo } })
+  expect(wrapper.find('img').exists()).toBe(false)
+  expect(wrapper.text()).toBe('供')
+  wrapper.unmount()
+})
+
+it.each([
+  [{ name: '工作代码', transport: 'http', url: 'https://api.githubcopilot.com/mcp' }, 'github'],
+  [{ name: '工作文档', transport: 'http', url: 'https://mcp.notion.com/mcp' }, 'notion'],
+  [{ name: '团队沟通', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-slack@1.0.0'] }, 'slack'],
+  [{ name: 'GitHub MCP 服务', transport: 'stdio', command: 'local-mcp' }, 'github'],
+  [{ name: '未知工具', transport: 'http', url: 'https://github.com.example.test/mcp' }, undefined],
+] as const)('recognizes MCP providers without matching lookalike hosts: %o', (definition, provider) => {
+  expect(mcpProviderLogo(definition as unknown as BotMcpPlugin)).toBe(provider ? `/provider-logos/${provider}.svg` : undefined)
+})

+ 9 - 0
tests/server/security.test.ts

@@ -78,6 +78,15 @@ describe('server security boundary', () => {
     expect(headers['Content-Security-Policy']).toContain('ws://yaoyao-lc.samien.cn')
     expect(headers['Content-Security-Policy']).toContain('ws://yaoyao-lc.samien.cn')
     expect(headers['Content-Security-Policy']).toContain('wss://yaoyao-lc.samien.cn')
     expect(headers['Content-Security-Policy']).toContain('wss://yaoyao-lc.samien.cn')
   })
   })
+  it('allows the supplier logo CDNs only in the image policy', () => {
+    const headers: Record<string, string> = {}
+    const ctx = { host: '127.0.0.1:15300', set(name: string, value: string) { headers[name] = value } } as unknown as Koa.Context
+    applySecurityHeaders(ctx, false)
+    const directives = headers['Content-Security-Policy'].split('; ')
+    expect(directives.find(value => value.startsWith('img-src '))).toBe("img-src 'self' data: blob: https://logos.composio.dev https://assets.composio.dev")
+    expect(directives.find(value => value.startsWith('script-src '))).toBe("script-src 'self'")
+    expect(directives.find(value => value.startsWith('connect-src '))).not.toContain('composio.dev')
+  })
 
 
   it('accepts configured IPv6 addresses and brackets them in WebSocket CSP origins', () => {
   it('accepts configured IPv6 addresses and brackets them in WebSocket CSP origins', () => {
     const allowed = new Set(['2001:db8::10'])
     const allowed = new Set(['2001:db8::10'])

+ 20 - 0
tests/server/workspacePlugins.test.ts

@@ -202,6 +202,26 @@ require('node:readline').createInterface({ input: process.stdin }).on('line', li
 })
 })
 
 
 describe('account-owned connected applications', () => {
 describe('account-owned connected applications', () => {
+  it('includes bundled supplier logos before configuring the connection service', async () => {
+    const result = await api('get', '/apps/catalog').expect(200)
+    expect(result.body.source).toBe('curated')
+    expect(result.body.cards).toHaveLength(12)
+    expect(result.body.cards.every((card: any) => card.logo === `/provider-logos/${card.slug}.svg`)).toBe(true)
+  })
+  it('preserves catalog logos from the approved image origins and falls back for invalid metadata', async () => {
+    await configure()
+    rejectRequest = url => url.includes('/toolkits?') ? response({ items: [
+      { slug: 'github', name: 'GitHub', meta: { logo: 'https://logos.composio.dev/api/github' } },
+      { slug: 'customapp', name: 'Custom', logo: 'https://assets.composio.dev/logos/custom.png' },
+      { slug: 'gmail', name: 'Gmail', meta: { logo: 'javascript:alert(1)' } },
+      { slug: 'unsafeapp', name: 'Unsafe', meta: { logo: 'https://logos.composio.dev.example.test/logo.svg' } },
+      { slug: 'credentialapp', name: 'Credentials', meta: { logo: 'https://user:secret@logos.composio.dev/api/secret' } },
+    ] }) : undefined
+    const result = await api('get', '/apps/catalog').expect(200)
+    expect(result.body.cards.map((card: any) => card.logo)).toEqual([
+      'https://logos.composio.dev/api/github', 'https://assets.composio.dev/logos/custom.png', '/provider-logos/gmail.svg', undefined, undefined,
+    ])
+  })
   it('supports catalog, OAuth links, multiple accounts, Bot grants and scoped disconnect', async () => {
   it('supports catalog, OAuth links, multiple accounts, Bot grants and scoped disconnect', async () => {
     await configure(); await configure('member')
     await configure(); await configure('member')
     const ownerSettings = runtime.workspace.list('owner', 'bot-plugin-settings')
     const ownerSettings = runtime.workspace.list('owner', 'bot-plugin-settings')

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů